JavaScript垃圾回收采用标记-清除算法,基于可达性分析:从根对象出发标记所有可访问对象,未标记的即被清除;它避免了引用计数因循环引用导致的内存泄漏问题。

JavaScript 的垃圾回收机制是自动管理内存的核心,它不靠开发者手动释放,而是由引擎判断哪些对象“已经没人要了”,然后悄悄收回内存。关键不是看谁还指着它,而是看它还能不能从根(比如全局对象、当前函数变量)被“找到”。标记-清除算法就是目前主流引擎(如 V8)实际采用的方式,它靠的是可达性分析,而不是数引用个数。
标记-清除算法怎么工作
整个过程分两步,没有中间状态:
- 标记阶段:从根对象出发(如 window、当前执行函数的局部变量、调用栈上的活跃对象),顺着所有引用链一路访问,把能碰到的对象都打上“活跃”标记;
- 清除阶段:扫一遍整个堆内存,把没被打标记的对象直接回收,它们占用的空间归还给空闲列表,下次 new 对象时可复用。
这个过程不关心对象之间有没有互相引用,只认“能不能从根走到它”。所以即使 obj1.ref = obj2 且 obj2.ref = obj1,只要函数执行完、两个变量都离开作用域,它们就无法从根到达 → 不会被标记 → 被清除。
为什么不用引用计数
引用计数听起来直观:谁指着我就+1,没人指着就回收。但它有个硬伤——循环引用会导致内存泄漏:
立即学习“Java免费学习笔记(深入)”;
- let a = {}; let b = {}; a.ref = b; b.ref = a;
- 此时 a 和 b 的引用计数都是 1;
- 即使后续 a = null; b = null;,它们仍互相“拉着”,计数不会归零;
- 结果是内存一直占着,却再也用不上。
现代 JS 引擎已不再用引用计数作为主算法,仅在极少数底层场景(如某些 COM 绑定)中辅助使用。
标记-清除带来的实际影响
理解它有助于写出更健壮的代码:
- 及时切断无用引用:比如事件监听器、定时器、DOM 引用,用完记得 removeEventListener、clearTimeout、置 null,否则它们可能意外维持对象存活;
- 避免闭包长期持有大对象:内层函数若捕获了外部的大数组或缓存,而该函数又被全局变量引用,整个数据就一直活在内存里;
- 注意 DOM 节点与 JS 对象的双向引用:比如给元素挂 .__data__ 属性存 JS 对象,又在对象里存 element 引用,容易形成隐式循环。
它不是实时发生的
GC 不是每赋值一次就跑一遍,而是由引擎按需触发:比如内存分配达到阈值、空闲时间检测到、或页面进入后台时。这也意味着你无法精确控制回收时机,但可以减少触发频率——比如复用对象、避免频繁创建临时大对象、及时清理不需要的引用。


















