背景
记一次项目内存泄漏优化。
通常我们在开发 Vue 的时候,大部分是在开发单页应用。但是,在单页应用的设计中,使用它是不需要刷新浏览器的,所以 JavaScript 应用需要自行清理组件来确保垃圾回收以预期的方式生效。通常我们在把其他库集成到应用时才会产生内存泄漏。
垃圾回收机制
要想解决内存泄漏问题,必须先了解浏览器的垃圾回收机制。
下面是摘抄的《高级程序设计》3 的片段
总的来说垃圾收集机制的原理:
垃圾收集器会按照固定的时间间隔,周期性的找出不再继续使用的变量,然后释放其占用的内存。
什么叫不再继续使用的变量?
不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。
全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收。全局变量最好不要用
垃圾回收 通用的 两种方式:标记清除:当前采用的垃圾收集策略
和引用计数
哪些情况会造成内存泄漏?
意外的全局变量
上面提到了全局变量不会被当成垃圾回收,但是我们在写代码时会出现下面这种情况:
1 | function foo() { |
解决方法:
1 | //方法1 |
未被清楚的定时器和回调函数
当不需要setInterval
或者setTimeout
时,定时器没有被 clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。
1 | let someResource = getData(); |
解决方法:
1 | let someResource = getData(); |
闭包
闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏。
1 | function bindEvent() { |
解决方法:手动解除引用,obj = null。
没有清理 DOM 元素引用
1 | var refA = document.getElementById("refA"); |
console 保存大量数据在内存中
过多的 console,比如定时器的 console 会导致浏览器卡死。
解决:合理利用 console,线上项目尽量少的使用 console,你也可以打包时全局关闭 console
如何避免内存泄漏
记住一个原则:不用的东西,及时归还,毕竟你是’借的’嘛。
减少不必要的全局变量,使用严格模式避免意外创建全局变量。
在你使用完数据后,及时解除引用(闭包中的变量,dom 引用,定时器清除)。
组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。
关于内存泄漏
即使是 1byte 的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
一般是堆区内存泄漏,栈区不会泄漏。
基本类型的值存在内存中,被保存在栈内存中,引用类型的值是对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏
。
Chrome 工具
Chrome 提供了一套很棒的检测 JavaScript 内存占用的工具。与内存相关的两个重要的工具:memory
和 performance
。
chrome 工具分析
先看性能分析
1、先固定时间线到 cpu 占用最高的地方
2、找到调用的方法
3、事件详情,可以看到我这里 Recalculate Style 重绘占用了 大部分 cpu
在 memory
标签下查看页面操作带来的内存变化
如果操作过程中发现内存有显著增加的情况,参考下面的方法
可以看到 DOM
显著增加、因为 DOM
复杂性比较高、所以 DOM GC
比较慢、当然这也是单页应用的痛点(内存会逐渐增加)
再看一段浏览器主动 GC
的片段
Minor GC
就是新生代 GC
,指发生在新生代的垃圾收集动作,所有的 Minor GC
都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。
总之在我们开发的过程中一定要注意页面的复杂性
Tips:
我们平常的 console 也是占用浏览器内存的,建议打包的时候把 console 去掉