web性能
什么是内存泄露
不再用到的对象内存,没有及时被回收时,就会产生内存泄露
常见的内存泄漏
- 不正常闭包
1
2
3
4
5
6
7
8
9
10
11function fn2(){
let test = new Array(1000).fill('isboyjc')
return function(){
console.log(test)
return test
}
}
let fn2Child = fn2()
fn2Child()
//return 的函数中存在函数 fn2 中的 test 变量引用,所以 test 并不会被回收,也就造成了内存泄漏,避免这个问题,后续添加fn2Child = null即可解决
fn2Child = null - 隐式全局变量
1
2
3
4
5
6
7
8function fn(){
// 没有声明从而制造了隐式全局变量test1
test1 = new Array(1000).fill('isboyjc1')
// 函数内部this指向window,制造了隐式全局变量test2
this.test2 = new Array(1000).fill('isboyjc2')
}
fn() - 游离DOM引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<div id="root">
<ul id="ul">
<li></li>
<li></li>
<li id="li3"></li>
<li></li>
</ul>
</div>
<script>
let root = document.querySelector('#root')
let ul = document.querySelector('#ul')
let li3 = document.querySelector('#li3')
// 由于ul变量存在,整个ul及其子元素都不能GC
root.removeChild(ul)
// 虽置空了ul变量,但由于li3变量引用ul的子节点,所以ul元素依然不能被GC
ul = null
// 已无变量引用,此时可以GC
li3 = null
</script> - 遗忘的定时器、监听器、监听者模式
1
2
3
4
5
6
7
8
9
10// 获取数据
let someResource = getData()
setInterval(() => {
const node = document.getElementById('Node')
if(node) {
node.innerHTML = JSON.stringify(someResource))
}
}, 1000)
//setInterval 没有结束前,回调函数里的变量以及回调函数本身都无法被回收。
//什么才叫结束呢?也就是调用了 clearInterval。如果没有被 clear 掉的话,就会造成内存泄漏。不仅如此,如果回调函数没有被回收,那么回调函数内依赖的变量也没法被回收。所以在上例中,someResource 就没法被回收。 - 遗忘的Map、Set对象
1
2
3
4
5
6// obj是一个强引用,对象存于内存,可用
let obj = {id: 1}
// 重写obj引用
obj = null
// 对象从内存移除,回收 {id: 1} 对象1
2
3
4
5
6
7
8
9
10
11
12let obj = {id: 1}
let user = {info: obj}
let set = new Set([obj])
let map = new Map([[obj, 'hahaha']])
// 重写obj
obj = null
console.log(user.info) // {id: 1}
console.log(set)
console.log(map)
<!-- 重写 obj 以后,{id: 1} 依然会存在于内存中,因为 user 对象以及后面的 set/map 都强引用了它,Set/Map、对象、数组对象等都是强引用,所以我们仍然可以获取到 {id: 1} ,我们想要清除那就只能重写所有引用将其置空了。 --> - 未清理的Console输出
内存泄漏排查、定位与修复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<button id="click">click</button>
<h1 id="content"></h1>
<script>
let click = document.querySelector("#click");
let content = document.querySelector("#content")
let arr = []
function closures() {
let test = new Array(1000).fill('isboyjc')
return function () {
return test
}
}
click.addEventListener("click", function () {
arr.push(closures())
arr.push(closures())
content.innerHTML = arr.length
});
</script>
</body>
</html>
- 快照
- Summary:按照构造函数进行分组,捕获对象和其使用内存的情况,可理解为一个内存摘要,用于跟踪定位DOM节点的内存泄漏
- Comparison:对比某个操作前后的内存快照区别,分析操作前后内存释放情况等,便于确认内存是否存在泄漏及造成原因
- Containment:探测堆的具体内容,提供一个视图来查看对象结构,有助分析对象引用情况,可分析闭包及更深层次的对象分析
- Statistics:统计视图
- Summary
- Constructor:显示所有的构造函数,点击每一个构造函数可以查看由该构造函数创建的所有对象
- Distance:显示通过最短的节点路径到根节点的距离,引用层级
- Shallow Size:显示对象所占内存,不包含内部引用的其他对象所占的内存
- Retained Size:显示对象所占的总内存,包含内部引用的其他对象所占的内存
- Comparison
- New:新建了多少个对象
- Deleted:回收了多少个对象
- Delta:新建的对象数 减去 回收的对象数
- performance工具
三个重要的名词
内存泄露
内存膨胀
频繁GC
1
GC 执行的特别频繁,一般出现在频繁使用大的临时变量导致新生代空间被装满的速度极快,而每次新生代装满时就会触发 GC,频繁 GC 同样会导致页面卡顿,想要避免的话就不要搞太多的临时变量,因为临时变量不用了就会被回收,这和我们内存泄漏中说避免使用全局变量冲突,其实,只要把握好其中的度,不太过分就 OK。
前端web性能指标
指标有点多,我们关注核心的几个
FCP (First Contentful Paint)首次内容绘制
1
2
3浏览器首次绘制来自DOM的内容的时间,内容必须包括文本,图片,非白色的canvas或svg,也包括带有正在加载中的web字体文本。这是用户第一次看到的内容。
注意:performance.timing已过时,无法获取所有异步加载资源。现在不适用了。LCP (Largest Contentful Paint)最大内容绘制
1
2
3
4
5
6//可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户的可见时间。img图片,video元素的封面,通过url加载到的北京,文本节点等,为了提供更好的用户体验,网站应该在2.5s以内或者更短的时间最大内容绘制。
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry);
}
}).observe({type: 'largest-contentful-paint', buffered: true});FID(First Input Delay), 可用TBT总阻塞时长代替衡量
1
2
3
4
5
6//首次输入延迟,从用户第一次与页面进行交互到浏览器实际能够响应该交互的时间,输入延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应用户,发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大量计算的JavaScript。
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('FID:', entry.processingStart - entry.startTime);
}
}).observe({type: 'first-input', buffered: true});CLS (Cumulative Layout Shift)
1
2
3
4
5
6
7
8
9
10//累计布局位移,CLS会测量在页面整个生命周期中发生的每个意外的布局移位的所有单独布局移位分数的总和,他是一种保证页面的视觉稳定性从而提升用户体验的指标方案。
用人话来说就是当点击页面中的某个元素的时候,突然布局变了,手指点到了其它位置。比如想点击页面的链接,突然出现了一个banner。这种情况可能是因为尺寸未知的图像或者视频。
let cls = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
}
}
}).observe({type: 'layout-shift', buffered: true});常用优化手法
减少 HTTP 请求
资源压缩
服务端渲染
将 CSS 放在文件头部,JavaScript 文件放在底部
1
2CSS 执行会阻塞渲染,阻止 JS 执行
JS 加载和执行会阻塞 HTML 解析,阻止 CSSOM 构建缓存
减少重绘重排
1
2
3
4
5
6
7
8
9----什么操作会导致重排?
添加或删除可见的 DOM 元素
元素位置改变
元素尺寸改变
内容改变
浏览器窗口尺寸改变
----如何减少重排重绘?
用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!