闭包是函数能访问并记住定义时词法作用域变量的运行时现象,因内层函数引用外层变量而形成,易致内存泄漏;需及时解绑事件、避免全局持有、清理定时器、限制缓存大小并销毁引用。

闭包是 JavaScript 中一个函数能访问并记住它定义时所在词法作用域的变量,即使这个函数在别的地方被调用。简单说,就是内部函数“带着”外部函数的局部变量一起跑出去了。
闭包是怎么形成的
当一个函数返回另一个函数(或把函数作为值传递),而这个被返回/传递的函数引用了外层函数的变量,闭包就产生了。
- 外层函数执行完后,其执行上下文本该销毁,但因为内层函数还“需要”里面的变量,引擎会保留这部分作用域
- 被保留的变量不会被垃圾回收,只要闭包还“活”着——比如被赋值给变量、绑定为事件、存进数组或对象里
- 闭包不是语法结构,而是运行时的现象;你写不写“closure”这个词,它都可能存在
闭包造成内存泄漏的关键原因
闭包本身不等于内存泄漏,但它会让变量“活得比预期久”。一旦这些变量体积大、数量多,又长期不释放,就会拖慢页面甚至卡死浏览器。
- DOM 元素 + 大数据同时被闭包持有:比如给按钮绑点击事件,回调里用了页面早已移除的大数组,数组和 DOM 节点都会卡在内存里
- 事件监听器没清理:组件卸载了,但闭包回调还挂在元素上,导致整个作用域链无法回收
- 全局变量意外持有了闭包:把闭包函数赋给 window 上的属性,等于给外部变量加了一道永久锁
- 定时器未清除:setInterval 的回调是闭包,如果忘记 clearInterval,它引用的数据就一直驻留
怎么判断是不是闭包引起的泄漏
打开 Chrome DevTools → Memory 面板 → 拍摄快照 → 操作页面(比如打开关闭模块)→ 再拍一张 → 对比“Detached DOM tree”和“Closure”类别的增长。如果某次操作后 Closure 数量猛增且不回落,大概率是闭包没断开引用。
立即学习“Java免费学习笔记(深入)”;
- 检查是否在事件绑定后忘了 removeEventListener
- 看有没有把函数直接挂到全局对象(如 window.handler = fn)
- 留意循环中创建的闭包是否共享了不该共享的变量(比如用 var 声明的 i)
实用的缓解方式
不是要消灭闭包,而是管好它的“寿命”和“胃口”。
- 事件处理完及时解绑:element.removeEventListener('click', handler) 或设为 null:element.onclick = null
- 避免在闭包里直接引用大型对象;可只传必要字段,或用 WeakMap 存储关联关系
- 缓存类闭包加上限机制,比如用 Map 实现 LRU 缓存,或设置最大条目数
- 组件销毁时主动清空闭包持有的资源,例如将闭包变量设为 null,切断引用链











