js内存泄漏本质是“意外保留强引用”,导致本该回收的对象因可被根对象间接访问而存活;聚焦定时器未清理、事件监听器未解绑、dom元素被变量强持、闭包持有大对象、全局变量挂载五类高频场景,并用chrome内存快照比对retaining path定位。

学JS内存泄漏,核心不是背概念,而是建立“引用—存活—回收失败”的直觉。重点不在知道有哪些泄漏类型,而在于能一眼看出某段代码为什么让对象活该被回收却没被回收。
从垃圾回收机制倒推泄漏逻辑
先搞清楚V8怎么判断一个对象该不该回收:它从根对象(比如 window、document、当前执行栈变量)出发,顺着所有引用链往下找,能到达的对象就标记为“存活”,其余一律清除。所以——
- 只要某个对象还能被根对象间接访问到,它就不会被回收
- 所谓“泄漏”,就是你主观上认为它该死了,但GC发现它还被某处悄悄拽着
- 所有泄漏场景,本质都是“意外保留了强引用”
聚焦五类高频泄漏模式,配可验证的最小示例
不用记七八种,掌握这五个就覆盖95%真实问题:
-
未清理的定时器:组件卸载后
setInterval仍在跑,它闭包里捕获的 DOM、state、API 响应数据全卡住不释放 -
事件监听器没解绑:用
addEventListener绑了,但没配对调用removeEventListener;尤其注意匿名函数无法移除 -
DOM 元素被 JS 变量强持:
const el = document.getElementById('x'); el.remove()后,el还在变量里,整个 DOM 树节点及其子孙都滞留内存 -
闭包长期持有大对象:比如缓存函数里用
Map存了千条数据,又没清理机制,闭包一活,Map 就一直活 -
全局变量挂载:漏写
let/const,或this.xxx = ...在非构造函数中执行,直接挂到window上,永不回收
用 Chrome DevTools 实操定位,三步闭环
光看代码猜不行,必须动手验证:
- 打开 Memory 面板 → 点击 “Take heap snapshot” 拍一张快照(A)
- 反复执行疑似泄漏的操作(如打开/关闭对话框、切换 tab)
- 再拍一张快照(B),在右上角选 “Comparison”,筛选
Objects allocated between Snapshot 1 and Snapshot 2 - 重点关注带
(detached)的 DOM 节点、大量重复的Closure、异常增长的Array或Object实例,点击展开看 retaining path(谁在引用它)
日常编码可落地的防御习惯
把预防变成肌肉记忆:
- 框架中写异步副作用(如
useEffect、onMounted)时,默认写清理函数,哪怕暂时空着 - 绑定事件优先用
once或明确命名的处理函数,避免匿名函数 - 需要缓存且生命周期不确定时,优先选
WeakMap/WeakSet,它们不计入 GC 引用计数 - 所有
setTimeout/setInterval都配上clearXXX,哪怕只是临时调试 - 文件顶部加
'use strict',让漏声明变量直接报错,而不是静默挂全局











