JS 的垃圾回收机制

1. 回收机制简介

JS 垃圾回收机制很简单:找出不再使用的变量,释放掉其占用的内存。但这个过程不是实时的——因为开销较大,垃圾回收系统(GC)会按照固定的时间间隔周期性地执行。

通常有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。

2. 标记清除(Mark and Sweep)

JS 最常用的 GC 方式。

当变量进入环境时(例如在函数中声明一个变量),就将这个变量标记为"进入环境"。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应环境就可能会用到它们。当变量离开环境时,将其标记为"离开环境"。

function test() {
  var a = 10; // 进入环境
  var b = 20; // 进入环境
}
test(); // 执行完毕后 a、b 被标记离开环境,被回收

3. 引用计数(Reference Counting)

跟踪记录每个值被引用的次数。当一个引用类型值被赋给变量时,引用次数为 1;赋给另一个变量加 1;引用变量取其他值减 1;引用次数为 0 时可被回收。

function test() {
  var a = {}; // a 引用次数为 0
  var b = a;  // a 引用次数 +1 = 1
  var c = a;  // a 引用次数 +1 = 2
  var b = {}; // a 引用次数 -1 = 1
}

缺陷:循环引用会导致内存无法回收(IE 早期 COM 对象问题)。现代引擎(V8)已弃用引用计数。

4. 如何分析内存使用情况

Chrome DevTools 提供 Memory / Performance 工具:

  • Memory 面板的 Heap snapshot:对 JS 运行时内存做快照并比较不同时间点的对象数量增长。
  • Performance 面板:观察 JS Heap 曲线是否单调上升。

每个结果视图提供:

  • summary 列表:按类型展示对象与合计大小(shallow size:类型所有对象总和;retained size:加上保留此对象的其他对象大小)。
  • comparison 视图:对比不同快照,对查找内存泄漏非常有用。

5. 怎样避免内存泄漏

  1. 减少不必要的全局变量,及时对无用的数据进行垃圾回收(变量用完置 null)。
  2. 注意程序逻辑,避免"死循环" / 长期持有的引用。
  3. 避免创建过多对象,原则:"不用了的东西要及时归还"。
  4. 清除定时器 / 事件监听器:组件销毁时调用 clearInterval、removeEventListener。
  5. 避免意外闭包:不要让闭包长期持有 DOM / 外部对象引用。

来源整理自:我的有道云笔记