闭包通过持有对外部变量的引用使变量“可达”,从而阻止垃圾回收;其形成需满足函数嵌套、引用外层变量、内层函数逃逸三个条件,不及时清理易致内存泄漏。

闭包的底层原理和垃圾回收机制是紧密咬合的一对关系——不是“有关”,而是“决定性影响”。关键点在于:闭包让变量变得“可达”,而JavaScript的垃圾回收只清理“不可达”的值。
词法作用域是闭包存在的前提
函数在定义时就确定了它能访问哪些变量,这个规则叫词法作用域。内部函数哪怕被返回到全局,依然能顺着定义时的作用域链找到外层的局部变量。
比如:
-
定义时绑定:
function outer() { let x = 10; return () => x; }中的箭头函数,在写出来那一刻就记住了x在哪; -
调用位置无关:即使把它赋给
window.fn,再在任意地方执行fn(),它仍能读到那个x; - 不是靠作用域“查找”,而是靠引用“持有”:引擎不是每次执行都去翻外层AO(活动对象),而是把需要的变量直接挂在闭包的[[Environment]]内部插槽里。
垃圾回收器靠“可达性”判断是否回收
JS不看“函数是否执行完”,而看“某个值是否还能被访问到”。只要有一个活的引用指向它,它就不可回收。
立即学习“Java免费学习笔记(深入)”;
闭包正是制造这种引用的关键方式:
- 当内部函数被返回、赋值给变量、传入事件监听器或定时器时,它就成为一个“外部活跃引用”;
- 这个函数内部持有着对外部变量的引用(例如
count),所以count变成“可达”; - 于是整个外层函数的词法环境(包括其他没被用到的变量)通常也无法释放——因为引擎按“整个词法环境”为单位管理,不会精细剔除未被引用的字段(V8等现代引擎有优化,但逻辑上仍以环境为粒度)。
闭包导致的内存驻留是自动且隐式的
你不需要手动保存变量,也不需要显式声明“我要闭包”。只要满足三个条件,闭包就自然形成并阻止回收:
- 函数嵌套(内层函数在外层函数体内定义);
- 内层函数引用了外层的局部变量或参数;
- 内层函数以某种方式逃逸出外层作用域(被返回、赋值给全局/对象属性/回调队列等)。
一旦满足,外层函数执行结束时,它的执行上下文不会被销毁,其中的变量就像被装进一个隐形背包,跟着内层函数走——这不是语法糖,是引擎基于可达性分析做出的真实内存保留行为。
不及时清理可能引发内存泄漏
闭包本身无害,但若引用长期存在又不再需要,就会拖住大量内存:
- 比如给DOM元素绑定事件,闭包中引用了大数组或整个组件实例,卸载组件时忘了
removeEventListener; - 又如缓存函数返回了带状态的闭包,但缓存项从未清除,导致旧数据一直驻留;
- 解决思路很直接:切断引用——把保存闭包的变量设为
null,或用WeakMap等弱引用结构隔离生命周期。


















