Vue内存泄漏优化

背景

记一次项目内存泄漏优化。

通常我们在开发 Vue 的时候,大部分是在开发单页应用。但是,在单页应用的设计中,使用它是不需要刷新浏览器的,所以 JavaScript 应用需要自行清理组件来确保垃圾回收以预期的方式生效。通常我们在把其他库集成到应用时才会产生内存泄漏。

垃圾回收机制

要想解决内存泄漏问题,必须先了解浏览器的垃圾回收机制。

下面是摘抄的《高级程序设计》3 的片段

1564156

总的来说垃圾收集机制的原理:

垃圾收集器会按照固定的时间间隔,周期性的找出不再继续使用的变量,然后释放其占用的内存。

什么叫不再继续使用的变量?

不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。

全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收。全局变量最好不要用

垃圾回收 通用的 两种方式:标记清除:当前采用的垃圾收集策略和引用计数

哪些情况会造成内存泄漏?

意外的全局变量

上面提到了全局变量不会被当成垃圾回收,但是我们在写代码时会出现下面这种情况:

1
2
3
4
5
function foo() {
this.bar2 = "默认绑定this指向全局"; // 全局变量=> window.bar2
bar = "全局变量"; // 没有声明变量 实际上是全局变量=>window.bar
}
foo();

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//方法1
function foo() {
this.bar2 = "默认绑定this指向全局";
let bar = "全局变量"; //改为局部变量
}
foo();
//方法2
function foo() {
this.bar2 = "默认绑定this指向全局";
bar = "全局变量";
}
foo();
window.bar = null; //手动释放
delete window.bar2;

未被清楚的定时器和回调函数

当不需要setInterval或者setTimeout时,定时器没有被 clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。

1
2
3
4
5
6
7
8
9
let someResource = getData();
setInterval(function() {
let node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
// 定时器也没有清除
}
// node、someResource 存储了大量数据 无法回收
}, 1000);

解决方法:

1
2
3
4
5
6
7
8
9
10
let someResource = getData();
let timer=setInterval(function() {
let node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
//
clearInterval(timer)
}
// node、someResource 存储了大量数据 无法回收
}, 1000);

闭包

闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏。

1
2
3
4
5
6
7
function bindEvent() {
var obj = document.createElement("XXX");
var unused = function () {
console.log(obj, "闭包内引用obj obj不会被释放");
};
// obj = null;
}

解决方法:手动解除引用,obj = null。

没有清理 DOM 元素引用

1
2
3
var refA = document.getElementById("refA");
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA"); // 但是还存在引用 能console出整个div 没有被回收

console 保存大量数据在内存中

过多的 console,比如定时器的 console 会导致浏览器卡死。

解决:合理利用 console,线上项目尽量少的使用 console,你也可以打包时全局关闭 console

如何避免内存泄漏

记住一个原则:不用的东西,及时归还,毕竟你是’借的’嘛。

减少不必要的全局变量,使用严格模式避免意外创建全局变量。
在你使用完数据后,及时解除引用(闭包中的变量,dom 引用,定时器清除)。
组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。

关于内存泄漏

即使是 1byte 的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
一般是堆区内存泄漏,栈区不会泄漏。
基本类型的值存在内存中,被保存在栈内存中,引用类型的值是对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏。

Chrome 工具

Chrome 提供了一套很棒的检测 JavaScript 内存占用的工具。与内存相关的两个重要的工具:memory 和 performance。

fhwoefhi

fdogjeorjgp

fheohhfow

chrome 工具分析

先看性能分析

fhoeiwfno

1、先固定时间线到 cpu 占用最高的地方
2、找到调用的方法
3、事件详情,可以看到我这里 Recalculate Style 重绘占用了 大部分 cpu

在 memory 标签下查看页面操作带来的内存变化

rhewriho

如果操作过程中发现内存有显著增加的情况,参考下面的方法

fhvfeoh

4235235

可以看到 DOM 显著增加、因为 DOM 复杂性比较高、所以 DOM GC 比较慢、当然这也是单页应用的痛点(内存会逐渐增加)

再看一段浏览器主动 GC 的片段

34724

Minor GC 就是新生代 GC,指发生在新生代的垃圾收集动作,所有的 Minor GC 都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。

总之在我们开发的过程中一定要注意页面的复杂性

Tips:
我们平常的 console 也是占用浏览器内存的,建议打包的时候把 console 去掉