JavaScript内存泄漏指本该被GC回收的对象因意外强引用链而滞留内存,常见于闭包持有DOM、全局变量滥用、未清除定时器及弱引用误用等场景。

JavaScript 中的内存泄漏不是“变量没被 delete”或“没手动释放”,而是**本该被垃圾回收的对象,因为意外的引用链一直存活在内存中**。只要存在从根(如全局对象、调用栈)出发的强引用路径,GC 就不会回收它。
闭包持有 DOM 引用后未清理
这是最隐蔽也最常见的泄漏点:闭包捕获了 DOM 元素,而该元素又被移除或替换,但闭包仍保有对它的引用,导致整个 DOM 子树无法释放。
典型场景:
- 为元素绑定事件回调,回调里用了外部变量(尤其是
this或父作用域变量) - 用
setTimeout/setInterval在回调中引用了 DOM 节点 - 第三方库(如旧版 jQuery)缓存了节点但未随节点销毁而清理
解决办法:
立即学习“Java免费学习笔记(深入)”;
- 事件监听器用完后显式调用
removeEventListener - 避免在定时器回调中直接引用大 DOM 对象;改用 ID 或轻量标识,运行时再查
- 组件卸载(如 React
useEffect清理、VuebeforeUnmount)时,手动清空闭包持有的 DOM 引用
全局变量和意外挂载到 window 上的对象
忘记写 var / let / const 声明变量,会导致它自动成为 window(浏览器)或 global(Node.js)的属性;或者显式赋值如 window.cacheData = {...},这些都会让对象长期驻留。
常见错误现象:
- 控制台打印
window发现一堆本不该存在的属性 - 页面反复进入/退出同一模块,内存占用持续上涨
检查与修复:
- 启用严格模式(
'use strict';),让漏声明变量直接报错 - 用 DevTools 的 Memory 面板录制 Allocation instrumentation on timeline,筛选出长期存活的大对象
- 避免任何对
window的随意写入;缓存逻辑统一走模块级Map或弱引用结构
定时器未清除 + 回调持有上下文
setInterval 是“泄漏加速器”——只要没调用 clearInterval,回调函数及其闭包就一直活着,连带其中所有引用的对象都无法回收。
尤其危险的是:
- 在单页应用路由切换时,组件销毁但
setInterval还在跑 - 回调里用了
this(指向组件实例)、document.querySelector结果、大型数据数组等
实操建议:
- 把定时器 ID 存在实例属性上(如
this.timerId = setInterval(...)),并在销毁逻辑中统一清理 - 优先用
requestIdleCallback或setTimeout模拟周期任务,更可控 - Node.js 环境下注意
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(ES2021)适合更细粒度控制:你可以用weakRef.deref()安全取值,返回undefined表示目标已被回收 - 不要试图用
WeakMap替代正常状态管理;它只适用于“附属、可丢失”的关联数据
真正难排查的泄漏往往藏在异步链深处:一个 Promise 的 catch 回调引用了已卸载组件的 this,或一个 WebSocket 的 onmessage 里闭包捕获了整个视图模型。靠工具(Chrome DevTools 的 Memory > Heap snapshot 对比)比靠直觉更可靠,但前提是得知道从哪几个引用链下手切。










