JavaScript-垃圾回收

垃圾回收机制

如果一个对象不再被引用, 那么这个对象就会被垃圾回收机制回收;如果两个对象互相引用, 且不再被第3者所引用, 那么这两个互相引用的对象也会被回收。在闭包中,父函数被子函数引用,子函数又被外部的一个变量引用,这就是父函数不被回收的原因。

如果不再用到的内存,没有及时释放,我们就称之为内存泄漏。大多数语言都有它自身的垃圾回收机制,这样的好处是自动帮我们清理不必要的内存占用,但是我们的可控性却比较差,而C语言就无法自动清理垃圾,但它的可控性较强。

在 Javascript 中,垃圾回收机制会定期(周期性)找出那些不再用到的内存(变量),然后释放其内存,以此来解决内存泄漏的问题。但并不是说有了垃圾回收机制,程序员就轻松了。你还是需要关注内存占用:那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用,如果没有引用就必须手动解除引用。 现在各大浏览器通常采用的垃圾回收机制有两种方法:标记清除,引用计数。js中最常用的垃圾回收方式就是标记清除。

标记清除

首先给所有的变量或者对象添加一个标记,当变量进入环境(引用变量)的时候,把上一步标记的内容清除,当变量离开环境(不再需要引用变量)的时候,再重新给这些变量添加标记,这些重新添加上标记的变量或对象会回收到垃圾回收机器里面,垃圾回收机制会周期性的清除这些垃圾回收机器里面的所有对象或属性。

引用计数

语言引擎有一张”引用表”,保存了内存里面所有资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。但是引用计数有个最大的问题循环引用,最好是在不使用它们的时候手动将它们设为空。

1
2
3
4
5
6
7
8
9
10
11
function func() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
// 手动清除
obj1 = null;
obj2 = null;
}

闭包的垃圾回收

由于闭包时建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数—也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后,作用域内的值也不会被销毁,这个函数的作用域就会一直保存到闭包不存在为止。

1
2
3
4
5
6
7
8
9
function fn3(){
var a = 10;
return function(){
a--;
console.log(a);
}
}
fn3()(); // 9
fn3()(); // 9

当fn3()()第一次执行完后,整个fn3()被销毁,第二次fn3()相当于重新开辟了一块新的空间,所以第二次fn3()()和第一次打印的结果无关。

1
2
3
4
5
6
7
8
9
10
11
function fn3(){
var a = 10;
return function(){
a--;
console.log(a);
}
}
var val = fn3();
val(); // 9
val(); // 8

当fn3第一次执行完后,val并没有被销毁,第二次是在第一次基础之上执行的,val指向的对象会永远存在堆内存中,即使是fn3已经执行完毕,需要 val=null 将其指向的对象释放。