js内存泄漏是本该释放的对象被意外持有,而非gc失灵;包括意外全局变量(如data = new array(1e6)导致window.data始终可达)、未清理定时器(setinterval持续引用obj使其被判“正在使用”)、dom引用未解绑(btn.remove()后仍强引用致子树残留)、闭包不当持有大对象(作用域链强引用使big无法标记清除)。

JS内存泄漏不是GC失灵,而是本该释放的对象仍被意外持有——面试时抓住这点,就能稳住逻辑主线。
高频泄漏场景与对应答题话术
面试官常问“哪些情况会导致内存泄漏”,直接按四类结构回答,每类带一句原理+一个极简例子:
-
意外全局变量:没用
let/const/var声明的变量自动挂到window,生命周期和页面一样长。
例:function leak() { data = new Array(1e6); }→data永远可达 -
未清理的定时器:
setInterval回调持续引用外部变量,即使组件已销毁。
例:const obj = {}; setInterval(() => console.log(obj), 1000);→obj无法回收 -
DOM引用未解绑:元素从DOM移除后,若JS仍持有其引用或绑定的事件监听器,整个子树残留。
例:const btn = document.getElementById('x'); btn.addEventListener('click', handler);→ 后续btn.remove()但没removeEventListener -
闭包不当持有大对象:内部函数长期存在,导致外层函数中本该释放的大数据被锁住。
例:function create() { const big = new Array(5e5); return () => console.log(big.length); }→big一直不释放
如何答出深度:补一句“为什么GC收不走”
每个例子后加半句解释,立刻拉开差距:
- 全局变量 → “GC只回收不可达对象,而
window.data始终可达” - 定时器 → “定时器回调是活跃执行上下文的一部分,它引用的
obj被判定为‘正在使用’” - DOM引用 → “DOM节点被JS变量强引用,即使脱离文档树,仍属‘可达对象’”
- 闭包 → “闭包形成了作用域链的强引用链,只要内层函数存在,外层变量就无法标记清除”
解决方案要具体、可落地
别只说“及时清理”,给出代码级动作:
- 全局变量 → 开启
'use strict',配合ESLint规则no-implicit-globals - 定时器 → 保存ID,组件卸载/离开页面前调用
clearInterval(id);或改用setTimeout递归 + 条件终止 - DOM事件 → 使用
addEventListener的第三个参数{once: true},或在清理函数中显式removeEventListener - 闭包 → 只保留必要字段,避免闭包捕获整个大对象;或手动置空引用:
closureRef = null
加分项:提一句排查手段
结尾自然带出实战意识:
线上可用performance.memory监控堆大小趋势;调试阶段用Chrome DevTools的Memory面板拍快照,对比操作前后“Retained Size”变化,重点看Detached DOM Tree和Closure条目。











