欢迎您访问新疆栾骏商贸有限公司,公司主营电子五金轴承产品批发业务!
全国咨询热线: 400-8878-609

新闻资讯

行业动态

JavaScript中的垃圾回收如何工作_内存管理有哪些策略

作者:幻影之瞳2025-12-31 00:00:00
JavaScript垃圾回收由引擎自动执行,采用标记-清除算法处理循环引用;WeakMap、WeakRef和FinalizationRegistry可辅助管理弱引用与清理逻辑;常见泄漏源于隐式强引用,需借助DevTools分析保留路径。

JavaScript 的垃圾回收不是你手动控制的,而是引擎自动运行的;它只回收「不再被引用」的对象,不会回收「还有变量指向它」的东西。哪怕你心里觉得“这数据没用了”,只要还存在隐式引用,就不会被收走。

标记-清除(Mark-and-Sweep)是主流实现方式

V8、SpiderMonkey、JavaScriptCore 等引擎都用这个基本策略:先标记所有「可访问」的对象(从全局对象、函数调用栈等根集出发遍历引用链),再清除所有未被标记的对象。

  • 它能正确处理循环引用——比如 a.ref = bb.ref = a,只要 ab 都不再被全局或栈中变量引用,整块内存就会被一并回收
  • 不依赖引用计数,所以避免了 IE6 时代因循环引用导致 DOM 对象无法释放的经典内存泄漏
  • 回收动作不是实时发生的,而是在内存压力上升或空闲时由引擎调度,因此你无法预测 delete 或赋值为 null 后内存何时真正释放

WeakMapWeakRef 是打破强引用的关键工具

它们允许你持有对象但不阻止 GC 回收——适合缓存、元数据绑定、事件监听器管理等场景。

  • WeakMap 的键必须是对象,且不阻止该对象被回收;一旦键被回收,对应条目自动消失
  • WeakRef(ES2025)让你拿到一个弱引用句柄,需调用 .deref() 才可能取到原对象;如果已被回收,.deref() 返回 undefined
  • 不能对 WeakRef 使用 === 判断是否相等,也不能用它做属性名或 Map 键
  • FinalizationRegistry 可注册回调,在对象被回收后触发清理逻辑(比如关闭底层资源),但不保证立即执行,也不保证一定执行

常见泄漏模式和应对方式

大多数内存问题不是 GC 失效,而是你不小心维持了本不该存在的引用。

  • 全局变量残留:window.cacheData = hugeArray —— 改成局部作用域或显式赋值 cacheData = null
  • 定时器未清除:setInterval(() => {...}, 1000) 中闭包捕获了大对象 → 清理时调用 clearInterval(id),并确保闭包不意外保留对外部作用域的引用
  • 事件监听器未解绑:使用 addEventListener 后忘记 removeEventListener,尤其在单页应用组件销毁时;推荐用 { once: true } 或用 AbortController 控制信号
  • 闭包中缓存 DOM 元素但未随元素移除而清理:改用 WeakMap 关联状态,或监听 DOMNodeRemoved(已废弃)/ 使用 MutationObserver
const cache = new WeakMap();
function decorateElement(el) {
  if (!cache.has(el)) {
    cache.set(el, { timestamp: Date.now(), computedStyle: getComputedStyle(el) });
  }
  return cache.get(el);
}
// el 被从 DOM 移除后,cache 中对应项会自动消失

真正难调试的从来不是「GC 没运行」,而是「你以为它该被回收,其实还挂着一根看不见的引用」——比如一个被闭包捕获的父级作用域,或者某个第三方库内部保存的回调数组。用 Chrome DevTools 的 Memory 面板拍堆快照,按「Retained Size」排序,点开「Retainers」看谁还在 hold 它,比猜要可靠得多。