闭包的本质是函数与其定义时的词法环境组成的组合体,是一种状态保留机制;它使内部函数在脱离原执行上下文后,仍能访问外层函数中本该被回收的自由变量,仅被引用的变量保留在堆内存中,而非整个执行上下文。

闭包的本质,就是函数与其定义时所处的词法环境(也就是执行上下文的一部分)共同构成的组合体。它不是“某个函数”,而是一种状态保留机制——当内部函数被外部引用并脱离原执行上下文后,仍能持续访问外层函数中本该销毁的变量。
闭包如何保护执行上下文环境
正常情况下,函数执行完毕,其执行上下文会从执行栈弹出,变量对象被垃圾回收。但若该函数内部返回了一个子函数,且这个子函数引用了外层函数的局部变量,JS引擎就会延长外层执行上下文的生命周期:不是整个上下文保留,而是其中的变量对象(VO)因被闭包引用而无法释放。
- 变量对象不会被销毁,仅因闭包持有对其的引用
- 作用域链被持续维护,确保内部函数调用时能沿链查找到外层变量
- this、arguments等上下文属性不参与闭包捕获,只有自由变量(非参数、非局部声明)被“带出”
哪些变量会被闭包保护
只有被内部函数实际引用的自由变量才会被保留在内存中。未被访问的外层变量,即使同属一个作用域,也不会因闭包存在而延迟回收。
- 例如:
function outer() { const a = 1; let b = 2; var c = 3; function inner() { return a + c; } return inner; }中,b不会被闭包保留,因为它未被inner引用 - 函数参数、
let/const声明的变量、var声明的变量,只要被引用,都可成为闭包的一部分 - 注意:箭头函数没有自己的
this和arguments,但它仍会捕获外层词法环境中的变量
执行上下文栈与闭包的共存关系
执行上下文栈只记录正在运行或待执行的上下文;而闭包维持的是对已退出上下文的**变量对象引用**,与栈无关。也就是说,outer函数早已出栈,但它的变量对象仍在堆内存中存活,供闭包函数随时读写。
- 栈清空 ≠ 内存释放:栈管理控制流,堆管理数据生命周期
- 闭包让变量对象从“临时栈帧”升格为“长期堆对象”
- 一旦闭包函数本身被解除引用(如
counter = null),且无其他引用指向该变量对象,它才可能被垃圾回收
常见误区与风险提示
闭包不是“自动保存所有外层变量”的黑箱,它的保护是精确且有代价的。滥用会导致意料之外的内存驻留。
- 误以为“函数里返回函数就一定形成闭包”——关键看是否引用了外层自由变量
- 在循环中不当使用闭包(如
var+setTimeout)会共享同一变量,造成值覆盖 - 闭包持有DOM节点或大型数据结构时,容易引发内存泄漏,尤其在事件监听器未清理的场景下











