闭包本身不导致内存泄漏,但被长期持有且捕获大对象时会阻止gc回收;常见场景包括未解绑事件监听器、未清除定时器、全局或长生命周期对象持有闭包、闭包过度捕获及多个闭包共享词法环境。

闭包让外部函数变量常驻内存,不是因为语法本身出错,而是因为变量仍处于“可触达”状态——只要闭包还被某个活跃对象引用着,它捕获的变量就无法被垃圾回收器释放。
事件监听器绑定后未解绑
闭包作为事件处理函数被挂载到 DOM 元素上,但元素被移除或页面切换后,忘记调用 removeEventListener。此时闭包依然存在,它所捕获的局部变量(比如大型数据、组件实例、DOM 节点)全部被强引用链锁住。
- 典型例子:按钮点击回调中引用了
const userData = fetchUser();,按钮被removeChild删除,但监听器没清理 →userData永远不释放 - SPA 中尤其危险:路由跳转后旧组件的闭包仍在全局事件总线或 document 上监听
闭包被赋值给全局或长生命周期对象
闭包被显式或隐式挂到 window、单例模块、日志中间件、缓存 Map、路由守卫等长期存活的对象上,导致其词法环境整个“钉”在内存里。
- 隐式泄漏:漏写
let/const,cache = new Map()自动变成全局变量,连带闭包一起永不释放 - 模块级泄漏:导出一个闭包工厂函数,内部缓存了初始化时创建的
new ArrayBuffer(100MB),该模块未卸载 → 缓存持续膨胀
多个闭包共享同一词法环境,但只用其中少量变量
外层函数定义了多个内层函数,它们共用一个词法环境。即使只有一个闭包真正用到了大对象,其余闭包哪怕完全弃用,引擎也无法按变量粒度拆解环境——整个作用域打包保留。
- React/Vue 场景常见:
useEffect或setup()中定义多个回调,其中一个用了hugeImageBuffer,其他回调虽未访问,仍拖住该 Buffer - 这不是代码错误,是 JS 引擎实现机制决定的:词法环境不可分割
定时器或 Promise 链中持有闭包
闭包被传入 setTimeout、setInterval 或作为 then 回调,且未在适当时机清除。只要定时器还在运行、Promise 链未终结,闭包就一直活跃。
- 常见陷阱:轮询接口时,闭包里保存了整个响应数据和配置对象;用户已离开页面,定时器仍在执行
- 异步链中闭包被
ref或reactive对象间接持有,形成隐蔽引用链











