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

本文详细探讨了前端开发中用户端检测、日志记录、异常/性能监控,特别是在大数据渲染、内存快照分析、内存泄露(如全局变量、定时器、闭包等)以及内存优化策略(如DOM渲染减少、数据懒加载、弱引用等)的重要性。通过实例和代码解析,提供了解决内存问题的方法和工具使用技巧。

目录

用户端检测

日志记录

异常/性能监控

用户反馈现象

内存占用濒临极限:卡顿

内存占用溢:崩溃

场景

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

B.大数据渲染

垃圾回收GC

标记:this.sdk=null (无引用,等待垃圾回收GC)

时机

自动

(如vue、React)框架自动移除所有模板绑定的事件(如 @click)

手动

 GC

全局事件、定时器(未执行)、第三方库实例、订阅/观察者

Memory :内存快照

关键项

内存分析:内存最高的点

内存泄露

挂载到 window 上全局变量

闭包

监听器,定时器

内存优化

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

数据懒加载

组件懒加载

虚拟滚动

数据分页

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

节流与防抖

console 导致的内存泄漏:引用

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

绑在 EventBus 的事件没有解绑

弱引用:weakset、weakmap

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

this.sdk.destory

参考链接:


用户端检测

日志记录

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

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

异常/性能监控

用户反馈现象

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

内存占用濒临极限:卡顿

内存占用溢:崩溃

场景

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

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

B.大数据渲染

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

treeList.png

垃圾回收GC

标记:this.sdk=null (无引用,等待垃圾回收GC)

时机

  • 内存不足时。

  • 浏览器空闲时(如 Chrome 的 V8 引擎的增量标记清除算法)。

  • 无固定时间,可能是几毫秒到几分钟,甚至更久。

自动

(如vue、React)框架自动移除所有模板绑定的事件(如 @click)

// Vue 组件
mounted() {
  window.addEventListener('resize', () => { 
    console.log(this.width); // 闭包引用组件实例 this
  });
},
beforeUnmount() {
  // 框架自动移除所有模板绑定的事件(如 @click)
  // 但需手动移除全局事件!
}
  • 若未手动移除 resize 监听:闭包中的 this 会导致组件实例泄漏。监听器函数通过 this 捕获了当前 组件实例

  • 若正确移除:闭包和 this 都会被回收。

手动

 GC

  1. 打开 Memory 面板 → 点击 Collect garbage(垃圾桶图标)。

  2. 执行 window.gc()(需启动 Chrome 时添加 --js-flags="--expose-gc" 参数)

// 强制 GC(非生产环境使用!)
if (window.gc) window.gc();

全局事件、定时器(未执行)、第三方库实例、订阅/观察者

现在多数是(SPA )单页面应用,长期运行,页面切换仅是组件替换,若开发者未主动清理资源,内存泄漏会随时间累积。

场景清理方式示例
全局事件监听window.removeEventListenerresize/scroll 事件
定时器clearInterval/clearTimeoutsetInterval 轮询
第三方库实例调用库的 destroy() 方法地图 SDK、图表库
订阅/观察者取消订阅(unsubscribe()Redux/RxJS

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()()

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

// 错误的写法: 闭包所引用的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)

this.sdk.destory

参考链接:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值