JavaScript垃圾回收机制已基本解决循环引用导致的内存泄漏问题,现代引擎采用标记-清除算法可回收不可达的循环引用对象;但隐式强引用(如未解绑事件监听器、未清理定时器、DOM与JS对象双向绑定)仍易引发内存泄漏。

JavaScript 的垃圾回收机制(GC)能自动处理大部分循环引用,但具体效果取决于引用类型和引擎实现,现代浏览器已基本解决传统循环引用导致的内存泄漏问题。
循环引用在 JS 中通常不会造成内存泄漏
早期 IE6–8 使用引用计数 GC,遇到对象 A 引用 B、B 又引用 A 的情况时,两者引用计数都不为 0,导致无法回收——这是经典的循环引用内存泄漏。但自 IE9 起,所有主流引擎(V8、SpiderMonkey、JavaScriptCore)都改用标记-清除(Mark-and-Sweep)算法为主,辅以增量/并发 GC 优化。该算法从根对象(如全局对象、当前执行上下文中的变量)出发,递归标记所有可达对象;未被标记的即为“不可达”,无论是否参与循环引用,都会被回收。
例如:
let objA = { name: 'A' };
let objB = { name: 'B' };
objA.ref = objB;
objB.ref = objA;
objA = null;
objB = null;
// 此时 A 和 B 已不可达,下次 GC 会回收二者及其循环引用
真正容易漏掉的是“隐式”循环引用
不是所有循环引用都安全。以下情况仍可能导致内存长期驻留:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 全局变量或闭包意外保留引用:比如事件监听器中闭包捕获了外部大对象,而监听器本身被 DOM 元素长期持有
- 定时器或 Promise 链持续持有作用域:setTimeout 回调里引用了大型对象,且定时器未清除
- WeakMap / WeakSet 外的 DOM 节点与 JS 对象双向绑定:如给 DOM 元素设置属性指向 JS 对象,同时 JS 对象又存着该元素引用
这类问题本质不是“循环引用本身难处理”,而是产生了本不该存在的强引用链,让对象意外保持可达。
主动规避的实用建议
虽然 GC 很强大,但写健壮代码仍需意识和习惯:
- 手动解绑不再需要的事件监听器(
element.removeEventListener) - 清理定时器(
clearTimeout/clearInterval) - 对需长期缓存但又不想阻碍回收的对象,优先使用
WeakMap存储元数据(键是对象,不阻止其被回收) - 避免在闭包中无必要地捕获大对象;必要时在逻辑结束前显式置
null
可借助工具验证实际回收情况
Chrome DevTools 的 Memory 面板支持录制堆快照(Heap Snapshot),可对比操作前后对象数量变化;也可用 Allocation instrumentation on timeline 观察某段代码是否持续分配未释放的对象。若发现某类对象数量只增不减,再顺藤摸瓜检查是否存在意外强引用。
不复杂但容易忽略。

















