闭包使变量不被垃圾回收,是因为内部函数持续引用外部变量,形成活跃引用链;只要闭包函数对象存在,其捕获的变量就无法释放,即使外部函数已执行完毕。

闭包能维持变量不被垃圾回收,核心在于变量仍被内部函数引用,形成活跃的引用链,使 JavaScript 引擎判定该变量“仍在使用中”,从而跳过垃圾回收。
闭包如何让变量“活下来”
当一个函数返回了内部定义的函数(即闭包),而这个内部函数又访问了外部函数的局部变量时,这些变量就不会随外部函数执行结束而销毁。引擎会保留外部函数的词法环境(Lexical Environment),因为闭包函数在后续调用中可能还需要读写它们。
- 变量是否被回收,不取决于“外部函数是否执行完”,而取决于“是否还有活跃的引用指向它”
- 闭包函数本身就是一个强引用:只要闭包函数对象还存在(比如被赋值给全局变量、被事件监听器持有、被定时器引用等),它所捕获的变量就无法被回收
- 即使外部函数早已返回,其活动对象(AO)或词法环境仍保留在内存中,只为支撑闭包的运行
常见导致变量“意外常驻”的情况
有些写法看似无害,实则让变量长期滞留内存,容易引发内存泄漏:
-
全局变量引用闭包:如
const keeper = outer();,只要keeper不被赋值为null或重新赋值,它捕获的所有变量都持续存在 -
DOM 事件绑定未解绑:闭包作为事件处理函数,且该函数引用了大对象(如缓存数据、整个 DOM 节点),即使节点被移除,若事件没手动
removeEventListener,闭包和它捕获的变量仍存活 -
定时器未清理:用
setInterval或setTimeout启动的闭包,若未调用clearInterval/clearTimeout,定时器回调保持活跃,连带捕获的变量也无法释放
怎么判断变量是否真被回收了
不能仅凭逻辑推断,需借助工具验证:
立即学习“Java免费学习笔记(深入)”;
- Chrome DevTools 中打开 Memory 面板 → 执行“Take Heap Snapshot” → 在快照中搜索变量名或构造函数名,查看是否有对应实例残留
- 关注“Retained Size”列:若某闭包对象的 Retained Size 较大,说明它正持有一大片内存无法释放
- 对比多次快照:重复操作(如打开关闭模块)后拍新快照,筛选“Objects allocated between snapshots”,看是否出现持续增长的闭包相关对象
主动释放闭包持有的变量
如果确实需要提前释放,关键不是“销毁闭包”,而是切断闭包对外部变量的引用:
- 将闭包引用设为
null:keeper = null;,解除变量到闭包函数的引用 - 在闭包内部主动清空大字段:
function myClosure() { let bigData = new Array(1000000); return () => { /* 使用后置空 */ bigData = null; } } - 避免在闭包中无意捕获不需要的变量——用立即执行函数或拆分作用域,缩小捕获范围
不复杂但容易忽略:闭包不是魔法,它只是引用关系的自然结果;变量活得久,是因为有人还在“惦记”它。


















