识别闭包内存泄漏的关键是看其是否“不该活却一直活着”并拖住大对象:定时器未清理、事件监听器未解绑、闭包捕获整块大对象、闭包挂载到长期存活对象,均会因强引用阻止垃圾回收。

识别闭包内存泄漏,关键不是看代码里有没有闭包,而是看它是否“不该活却一直活着”,并拖住了本该释放的大对象。真正危险的不是闭包本身,而是它背后隐含的强引用关系。
函数内创建定时器但没清理
常见于组件挂载时启动轮询或状态同步,卸载后仍运行:
-
典型写法:
const timer = setInterval(() => { console.log(this.data) }, 1000),但没在销毁阶段调用clearInterval(timer) -
风险点:闭包捕获了
this、大型数组、响应式 store 或 DOM 节点;timer 持有该闭包,导致整个作用域无法回收 - 信号表现:页面切换后控制台仍在打印日志;反复打开同一模块,内存曲线阶梯式上升
事件监听器用匿名/箭头函数绑定
监听器绑得快,解得难,尤其在动态 DOM 或组件生命周期中:
-
典型写法:
btn.addEventListener('click', () => doSomething(el, data)),后续未调用removeEventListener -
风险点:闭包捕获了外部
el(DOM 节点)、data(大对象),而元素被remove()后,监听器仍通过事件系统被全局持有 -
信号表现:Elements 面板中目标元素已无节点,但 Event Listeners 标签页仍有残留;快照中 Closure 的 Retainers 显示
EventListener → function → DOM element
闭包意外捕获整块大对象而非必要字段
看似只读一个属性,实则锁住整个数据结构:
-
典型写法:
const handler = () => console.log(bigList.length),其中bigList是百万级数组 -
风险点:闭包词法作用域捕获的是变量
bigList的引用,不是它的length值;即使只用一个数字,整个数组也被强持有 -
优化方式:改用显式提取
const len = bigList.length; const handler = () => console.log(len),或通过参数传入必要片段
把闭包挂到长期存活对象上
闭包成了“常驻居民”,随宿主一起永不释放:
-
典型写法:
window.globalHandler = () => console.log(config),或emitter.on('event', handler)却忘了off -
风险点:闭包捕获了全局配置、缓存 Map、单例实例等;只要宿主(如
window、emitter)不销毁,闭包及其捕获物就永远在内存里 -
信号表现:快照中 Closure 的 Retainers 首层就是
window或某个长生命周期类实例;多次操作后同类 Closure 的#New持续增长











