JS会在创建变量时自动分配内存,在不使用的时候会自动周期性的释放内存,释放的过程就叫 “垃圾回收”。
一方面自动分配内存减轻了开发者的负担,开发者不用过多的去关注内存使用,但是另一方面,正是因为因为是自动回收,所以如果不清楚回收的机制,会很容易造成混乱,而混乱就很容易造成”内存泄漏”。
由于是自动回收,所以就存在一个 “内存是否需要被回收的” 的问题,但是这个问题的判定在程序中意味着无法通过某个算法去准确完整的解决,后面探讨的回收机制只能有限的去解决一般的问题。
回收算法
垃圾回收对是否需要回收的问题主要依赖于对变量的判定是否可访问,由此衍生出两种主要的回收算法:
- 标记清理
- 引用计数
标记清理
标记清理是js最常用的回收策略,2012年后所有浏览器都使用了这种策略,此后的对回收策略的改进也是基于这个策略的改进。其策略是:
- 变量进入上下文,也可理解为作用域,会加上标记,证明其存在于该上下文;
- 将所有在上下文中的变量以及上下文中被访问引用的变量标记去掉,表明这些变量活跃有用;
- 在此之后再被加上标记的变量标记为准备删除的变量,因为上下文中的变量已经无法访问它们;
- 执行内存清理,销毁带标记的所有非活跃值并回收之前被占用的内存;
局限:
- 由于是从根对象(全局对象)开始查找,对于那些无法从根对象查询到的对象都将被清除
- 回收后会形成内存碎片,影响后面申请大的连续内存空间
引用计数
引用计数策略相对而言不常用,因为弊端较多。其思路是对每个值记录它被引用的次数,通过最后对次数的判断(引用数为0)来决定是否保留,具体的规则有:
- 声明一个变量,赋予它一个引用值时,计数+1;
- 同一个值被赋予另外一个变量时,引用+1;
- 保存对该值引用的变量被其他值覆盖,引用-1;
- 引用为0,回收内存;
局限:
最重要的问题就是,循环引用 的问题
function refProblem() {
let a = new Object();
let b = new Object();
a.c = b;
b.c = a; //互相引用
}
根据之前提到的规则,两个都互相引用了,引用计数不为0,所以两个变量都无法回收。如果频繁的调用改函数,则会造成很严重的内存泄漏。