1.概念
JS会在创建变量时自动分配内存,在不使用的时候会自动周期性的释放内存,释放的过程就叫 “垃圾回收”。
优点:自动分配内存减轻了开发者的负担,开发者不用过多的去关注内存使
缺点:因为是自动回收,所以如果不清楚回收的机制,会很容易造成混乱,而混乱就很容易造成"内存泄漏".由于是自动回收,所以就存在一个 “内存是否需要被回收的” 的问题,
2.回收算法
2.1 标记清理
步骤
- 1.变量进入上下文,会加上标记,证明其存在于该上下文;
- 2.将所有在上下文中的变量以及上下文中被访问引用的变量标记去掉,表明这些变量活跃有用;
- 3.在此之后再被加上标记的变量标记为准备删除的变量,因为上下文中的变量已经无法访问它们;
- 4.执行内存清理,销毁带标记的所有非活跃值并回收之前被占用的内存;
优点
标记清除算法的优点只有一个,那就是实现比较简单
,打标记也无非打与不打两种情况,这使得一位二进制位(0和1)就可以为其标记,非常简单
缺点
在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了 内存碎片
,并且由于剩余空闲内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配
的问题
补充:标记整理(Mark-Compact)算法
- 它的标记阶段和标记清除算法没有什么不同,只是标记结束后,标记整理算法会将活着的对象向内存的一端移动,最后清理掉边界的内存
2.2 引用计数
步骤
引用计数策略相对而言不常用,因为弊端较多。其思路是对每个值记录它被引用的次数,通过最后对次数的判断(引用数为0)来决定是否保留,具体的规则有
- 1.声明一个变量,赋予它一个引用值时,计数+1;
- 2.同一个值被赋予另外一个变量时,引用+1;
- 3.保存对该值引用的变量被其他值覆盖,引用-1;
- 4.引用为0,回收内存;
优点
引用计数算法的优点我们对比标记清除来看就会清晰很多,首先引用计数在引用值为 0 时,也就是在变成垃圾的那一刻就会被回收,所以它可以立即回收垃圾
而标记清除算法需要每隔一段时间进行一次,那在应用程序(JS脚本)运行过程中线程就必须要暂停去执行一段时间的 GC,另外,标记清除算法需要遍历堆里的活动以及非活动对象来清除,而引用计数则只需要在引用时计数就可以了
缺点(循环引用)
function refProblem () {
let a = new Object();
let b = new Object();
a.c = b;
b.c = a; //互相引用
}
两个对象都互相引用了,引用计数不为0,所以两个变量都无法回收。如果频繁的调用改函数,则会造成很严重的内存泄漏。
3.常见的内存泄露
3.1意外声明全局变量
function hello (){
name = 'tom'
}
hello();
未声明的对象会被绑定在全局对象上,就算不被使用了,也不会被回收,所以写代码的时候,一定要记得声明变量。
3.2定时器
let name = 'Tom';
setInterval(() => {
console.log(name);
}, 100);
定时器的回调通过闭包引用了外部变量,如果定时器不清除,name会一直占用着内存,所以用定时器的时候最好明白自己需要哪些变量,检查定时器内部的变量,另外如果不用定时器了,记得及时清除定时器。
3.3 闭包
let out = function() {
let name = 'Tom';
return function () {
console.log(name);
}
}
由于闭包会常驻内存,在这个例子中,如果out一直存在,name就一直不会被清理,如果name值很大的时候,就会造成比较严重的内存泄漏。所以一定要慎重使用闭包。
3.4 事件监听
mounted() {
window.addEventListener("resize", () => {
//do something
});
}
在页面初始化时绑定了事件监听,但是在页面离开的时候未清除事监听,就会导致内存泄漏。