JavaScript内存泄漏指本该被GC回收的对象因意外强引用链而滞留内存,常见于闭包持有DOM、全局变量滥用、未清除定时器及弱引用误用等场景。
JavaScript 中的内存泄漏不是“变量没被 delete”或“没手动释放”,而是**本该被垃圾回收的对象,因为意外的引用链一直存活在内存中**。只要存在从根(如全局对象、调用栈)出发的强引用路径,GC 就不会回收它。
这是最隐蔽也最常见的泄漏点:闭包捕获了 DOM 元素,而该元素又被移除或替换,但闭包仍保有对它的引用,导致整个 DOM 子树无法释放。
典型场景:
this 或父作用域变量)setTimeout / setInterval 在回调中引用了 DOM 节点解决办法:
removeEventListener
useEffect 清理、Vue beforeUnmount)时,手动清空闭包持有的 DOM 引用window 上的对象忘记写 var / let / const 声明变量,会导致它自动成为 window(浏览器)或 global(Node.js)的属性;或者显式赋值如 window.cacheData = {...},这些都会让对象长期驻留。
常见错误现象:
window 发现一堆本不该存在的属性检查与修复:
'use strict';),让漏声明变量直接报错window 的随意写入;缓存逻辑统一走模块级 Map 或弱引用结构setInterval 是“泄漏加速器”——只要没调用 clearInterval,回调函数及其闭包就一直活着,连带其中所有引用的对象都无法回收。
尤其危险的是:
setInterval 还在跑this(指向组件实例)、document.querySelector 结果、大型数据数组等实操建议:
this.timerId = setInterval(...)),并在销毁逻辑中统一清理requestIdleCallback 或 setTimeout 模拟周期任务,更可控setInterval 会阻止进程退出,必须 clearInterval
WeakMap 和 WeakRef 管理临时关联当必须为某个对象(比如 DOM 元素)附加元数据,又不想阻止它被回收时,传统 Map 会形成强引用,而 WeakMap 只接受对象作为键,且不阻止键的回收。
示例:
const elementData = new WeakMap();
function attachMetadata(el, data) {
elementData.set(el, data); // el 被回收后,对应 entry 自动消失
}
const div =
document.createElement('div');
attachMetadata(div, { id: 'user-card', loaded: true });
document.body.appendChild(div);
// 后续 div.remove() → div 对象可被 GC,elementData 中的 entry 也随之失效
注意:
WeakMap 键必须是对象,不能是字符串或数字WeakRef(ES2025)适合更细粒度控制:你可以用 weakRef.deref() 安全取值,返回 undefined 表示目标已被回收WeakMap 替代正常状态管理;它只适用于“附属、可丢失”的关联数据真正难排查的泄漏往往藏在异步链深处:一个 Promise 的 catch 回调引用了已卸载组件的 this,或一个 WebSocket 的 onmessage 里闭包捕获了整个视图模型。靠工具(Chrome DevTools 的 Memory > Heap snapshot 对比)比靠直觉更可靠,但前提是得知道从哪几个引用链下手切。