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 内存占用的工具。与内存相关的两个重要的工具:memoryperformance

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 去掉