目录
用户端检测
日志记录
在应用程序中添加详细的日志记录,特别是关于内存使用和释放的信息
内存占用持续增加而不减少
异常/性能监控
用户反馈现象
CPU 分配给浏览器的内存空间是很小且有限的
内存占用濒临极限:卡顿
内存占用溢:崩溃
场景
A.配置低,经常出现页面崩溃
页面内存占用太大,打开几个页面后,内存直接拉满
B.大数据渲染
左侧是一个 Tree 树形控件,该控件一次性加载了三千条数据。难以置信,该页面的内存竟然到了113M
,而改为懒加载子节点后,该页面的内存直接降到了15M
,内存的前后差异是惊人的
Memory :内存快照
1)打开 chrome 浏览器控制台,选择Memory
工具
2)点击左侧start按钮
,刷新页面,开始录制的JS堆动态分配时间线
,会生成页面加载过程内存变化的柱状统计图(蓝色表示未回收,灰色表示已回收)
关键项
Constructor:对象的类名;
Distance:对象到根的引用层级;
Objects Count:对象的数量;
Shallow Size: 对象本身占用的内存,不包括引用的对象所占内存;
Retained Size: 对象所占总内存,包含引用的其他对象所占内存;
Retainers:对象的引用层级关系
// 测试代码
class Jane {}
class Tom {
constructor() {
this.jane = new Jane();
}
}
let list = Array(1000000)
.fill('')
.map(() => new Tom());
shallow size 和 retained size 的区别,以用红框里的 Tom
和 Jane
更直观的展示
Tom 的 shallow 占了 32M,retained 占用了 56M,这是因为 retained 包括了引用的指针对应的内存大小,即 tom.jane
所占用的内存
所以 Tom 的 retained 总和比 shallow 多出来的 24M,正好跟 Jane 占用的 24M 相同
内存分析:内存最高的点
1)从柱状图中找到最高的点,重点分析该时间内造成内存变大的原因
2)按照Retainers size
(总内存大小)排序,点击展开内存最高的哪一项,点击展开构造函数,可以看到所有构造函数相关的对象实例
3)选中构造函数,底部会显示对应源码文件,点击源码文件,可以跳转到具体的代码,这样我们就知道是哪里的代码造成内存过大
4)结合具体的代码逻辑,来判断这块内存变大的原因,重点是如何去优化它们,降低内存的使用大小
点击keyghost.js
可以跳转到具体的源码
内存泄露
意外的全局变量, 挂载到 window 上全局变量
遗忘的定时器,定时器没有清除
闭包
内存优化
减少组件DOM渲染(首要原因)
数据懒加载
组件懒加载
虚拟滚动
数据分页
window上的监听事件没有移除或移除错误
节流与防抖
// 版本一
mounted() {
window.addEventListener('resize', debounce(this.fn, 100))
},
beforeDestroy() {
window.removeEventListener('resize', debounce(this.fn, 100))
}
每次调用debounce(this.fn, 100)
时, 其实都会返回一个新的函数,导致 addEventListener 和 removeEventListener 方法传入的回调函数已经不是同一个函数
// 版本二
data() {
return {
debounceFn: null
}
},
mounted() {
this.debounceFn = debounce(this.fn, 100)
window.addEventListener('resize', this.debounceFn)
},
beforeDestroy() {
window.removeEventListener('resize', this.debounceFn)
}
console 导致的内存泄漏:引用
因为 list 数组被 console 所引用,导致 list 内存不能被释放
function fn () {
let list = new Array(10 * 1024 * 1024).fill(1); // 约占42M内存
return function () {
console.log(list)
}
}
fn()()
闭包的错误使用:所引用的变量在函数外部
绝大多数情况,只要引用闭包的函数被正常销毁,闭包所占的内存都会被 gc 自动回收
特别是现在 SPA 项目的盛行,用户在切换页面时,老页面的组件会被框架自动清理,所以我们可以放心大胆的使用闭包,无需多虑
// 错误的写法: 闭包所引用的info变量在函数外部
let info = {
arr: new Array(10 * 1024 * 1024).fill(1),
timer: null
};
export const debounce = (fn, time) => {
// 正确的写法: 闭包所引用的info变量在函数内部
let info = {
arr: new Array(10 * 1024 * 1024).fill(1),
timer: null
};
return function (...args) {
info.timer && clearTimeout(info.timer);
info.timer = setTimeout(() => {
fn.apply(this, args);
}, time);
};
};
绑在 EventBus 的事件没有解绑
mounted () {
this.$EventBus.$on('homeTask', res => this.fn(res))
},
destroyed () {
this.$EventBus.$off()
}
弱引用:weakset、weakmap
它们对值的引用都是不计入垃圾回收机制的,如果其他对象都不再引用该对象,那么gc 会自动回收该对象所占用的内存
注册监听事件的 listener 对象: WeakMap
由于监听函数是放在 WeakMap 里面,一旦 element 对象的其他引用消失,与它绑定的监听函数 handler 所占的内存也会被自动释放
// 代码1
element.addEventListener('click', handler, false)
// 代码2
weak.set(element, handler)
element.addEventListener('click', weak.get(element), false)
参考链接: