JavaScript通过自动内存管理实现内存分配和闲置资源回收,基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。
全局变量会在浏览器或者页面关闭的时候释放,而局部变量只会在函数执行过程中存在,所以垃圾回收一般说的是函数内部的变量回收机制。

这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。
在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。

标记清理

垃圾回收程序运行的时候,会标记变量当前是进入环境还是离开环境,接着排除全局变量和闭包AO中的变量,然后开始清理。

  1. function test() {
  2. var a = 0; // 标记进入环境
  3. }
  4. test(); // 标记 a 离开环境
  5. var b = 0;
  6. var c = 1;
  7. // 最后排除全局和闭包AO中的变量,然后清理标志离开环境的变量

引用计数

引用计算是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。

  1. function test() {
  2. var a = new Object(); // a = 1;
  3. var b = new Object(); // b = 1;
  4. var c = a; // a + 1 = 2
  5. c = b; // a - 1 = 1, b + 1 = 2;
  6. }
  7. test();

引用计数很快就遇到了严重的问题:循环引用。所谓循环引用,就是对象 A 有一个指针指向对象 B,而对象 B 也引用了对象 A。

  1. function test() {
  2. var a = new Object(); // a = 1;
  3. var b = new Object(); // b = 1;
  4. // 循环引用
  5. a.prop = b; // b = 2
  6. b.prop = a; // a = 2
  7. // 解除引用
  8. a = null;
  9. b = null;
  10. }
  11. test();
  1. 以上代码 `a``b`在函数结束后还会存在,因为它们的引用数永远不会变成 0。如果函数被多次调用,则会导致大量内存永远不会被释放。