JavaScript闭包导致内存泄漏的常见场景
闭包被长期持有时若捕获大对象,将导致内存泄漏。常见场景包括:事件监听器未解绑、定时器未清除、全局对象持有闭包、闭包捕获多余大对象。应适时清理监听器与定时器,用参数传值替代捕获,对大对象手动切断引用。

那么,在真实项目里,哪些场景最容易“中招”?
事件监听器没解绑
这是最普遍的泄漏源头。用闭包函数做点击、滚动之类的回调,绑定完后组件却卸掉了,但 removeEventListener 根本没执行。这下好,闭包连带它捕获的整个作用域——DOM 节点、state、大型数组——全卡在内存里走不了。
- 单页应用里,页面反复切换,很容易累积一堆没清理的监听器
- 异步加载后动态绑定的监听器,更是常漏掉清理那一步
- 推荐统一管理:React 用 useEffect 清理函数,Vue 用 beforeUnmount,原生 JS 则维护一个 listeners 数组来批量清除
定时器没清除
setInterval 或 setTimeout 的回调本质上就是闭包。只要它还在跑,回调里访问过的 this、DOM 元素、bigData 就一个都送不走。
- 典型表现:路由跳走后,控制台还在打印日志,或者内存面板上数字持续增长
- 务必在组件销毁、页面离开前调用 clearInterval/clearTimeout
- 另外,尽量别让闭包直接引用大对象。必要时只传个 ID 或轻量字段进去,而不是整个数据结构
全局或长生命周期对象持有闭包
把闭包赋给 window、模块导出对象或 class 实例属性,等于给它发了张“永久居留证”。宿主不倒下,闭包和它捕获的所有变量就别想被回收。
比如缓存函数写成 cache[key] = () => doSomething(largeData),largeData 就一直被锁住。这种情况下,优先用 WeakMap 来替代——当键对象被回收后,对应的缓存条目会自动失效。如果非得用全局缓存,加个 TTL 过期机制,或者手动设 null 清理。
闭包捕获了不该捕获的大对象
一个轻量的回调,因为写法松散,可能偷偷背走了整棵树。比如循环中用 var 声明变量再传入闭包,所有闭包都共享同一个变量,不仅逻辑容易出乱子,还延长了整个作用域的生命周期。
多个闭包共享一个外层作用域时,哪怕只用其中一个函数,所有被捕获的数据(config、cache、largeArray)也全都得留着。优化方向很清楚:按需拆分闭包,用参数传值替代捕获,对大对象手动切断引用(比如设为 null)。排序、搜索这类算法里,就要避免在闭包中直接引用百万级数组,改用索引或 ID 间接操作。
