前端内存分析、优化、检测泄露

51 篇文章 14 订阅
3 篇文章 0 订阅

目录

用户端检测

日志记录

异常/性能监控

用户反馈现象

内存占用濒临极限:卡顿

内存占用溢:崩溃

场景

A.配置低,经常出现页面崩溃

B.大数据渲染

Memory :内存快照

关键项

内存分析:内存最高的点

内存泄露

意外的全局变量, 挂载到 window 上全局变量

遗忘的定时器,定时器没有清除

闭包

内存优化

减少组件DOM渲染(首要原因)

数据懒加载

组件懒加载

虚拟滚动

数据分页

window上的监听事件没有移除或移除错误

节流与防抖

console 导致的内存泄漏:引用

闭包的错误使用:所引用的变量在函数外部

绑在 EventBus 的事件没有解绑

弱引用:weakset、weakmap

注册监听事件的 listener 对象: WeakMap 

用户端检测

日志记录

在应用程序中添加详细的日志记录,特别是关于内存使用和释放的信息

内存占用持续增加而不减少

异常/性能监控

用户反馈现象

CPU 分配给浏览器的内存空间是很小且有限的

内存占用濒临极限:卡顿

内存占用溢:崩溃

场景

A.配置低,经常出现页面崩溃

页面内存占用太大,打开几个页面后,内存直接拉满

B.大数据渲染

左侧是一个 Tree 树形控件,该控件一次性加载了三千条数据。难以置信,该页面的内存竟然到了113M,而改为懒加载子节点后,该页面的内存直接降到了15M,内存的前后差异是惊人的

treeList.png

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 的区别,以用红框里的 TomJane 更直观的展示

Tom 的 shallow 占了 32M,retained 占用了 56M,这是因为 retained 包括了引用的指针对应的内存大小,即 tom.jane 所占用的内存

所以 Tom 的 retained 总和比 shallow 多出来的 24M,正好跟 Jane 占用的 24M 相同

内存分析:内存最高的点

1)从柱状图中找到最高的点,重点分析该时间内造成内存变大的原因

2)按照Retainers size(总内存大小)排序,点击展开内存最高的哪一项,点击展开构造函数,可以看到所有构造函数相关的对象实例

3)选中构造函数,底部会显示对应源码文件,点击源码文件,可以跳转到具体的代码,这样我们就知道是哪里的代码造成内存过大

4)结合具体的代码逻辑,来判断这块内存变大的原因,重点是如何去优化它们,降低内存的使用大小

retainedSize.jpg

点击keyghost.js可以跳转到具体的源码

localkey.png

内存泄露

意外的全局变量, 挂载到 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)

参考链接:

「历时8个月」10万字前端知识体系总结(工程化篇)🔥 - 掘金

前端内存优化知多少?内存泄露只是冰山一角 - 掘金

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值