闭包是javascript执行机制自然产生的结果:函数携带对其定义时作用域的引用而形成,由执行上下文创建、作用域链建立与保留共同完成;其本质是静态词法作用域与动态变量驻留的结合,非语法糖或技巧。

闭包的本质,不是语法糖,也不是某种“技巧”,而是 JavaScript 执行机制在运行时自然产生的结果:当一个函数被创建并携带了对其定义时所在作用域的引用,它就构成了闭包。这个过程由执行上下文的生成、作用域链的建立与保留共同完成——关键不在“怎么写”,而在“怎么活”。
执行上下文:闭包诞生的温床
每次函数调用,JS 引擎都会新建一个函数执行上下文。它包含三部分:变量对象(VO)、作用域链(Scope Chain)和 this 绑定。其中,作用域链在上下文创建阶段就被静态确定:从当前函数的[[Environment]]开始,逐级向上链接到外层函数的变量对象,最终指向全局对象。
- 这个链不是运行时动态拼接的,而是在函数定义时就固化下来的词法结构
- 即使外层函数执行完毕、其执行上下文本该被销毁,只要内部函数仍被引用(比如被返回、赋值给变量、传入回调),引擎就必须保留它的作用域链
- 于是,外层函数的变量对象(含局部变量、参数)无法被垃圾回收——它们被“挂”在闭包的作用域链上继续存活
作用域链:闭包的内存锚点
作用域链是闭包能持续访问外部变量的唯一路径。它不是一个副本,而是一组真实存在的引用。例如:
function makeAdder(x) {
return function(y) { return x + y };
}
const add5 = makeAdder(5);
此时 add5 的作用域链中,第一项是它自己的活动对象(空),第二项是 makeAdder(5) 的变量对象(含 x: 5),第三项是全局对象。x 并未被复制进 add5 内部,而是通过这条链实时查找。
- 每次调用 add5(3),引擎都沿着这条链找到 x,再做加法
- 如果 x 是对象,闭包访问的是同一个对象引用——修改它会影响所有共享该闭包的调用
- 作用域链越深,变量查找耗时略增,但这是语义必需的开销,不可绕过
动态绑定?其实是静态词法 + 运行时驻留
常有人说“闭包是动态绑定”,这容易误解。准确说是:作用域关系静态确定,变量生命动态延续。函数定义的位置决定了它能访问哪些外部变量(词法作用域),而变量是否真的留在内存里,取决于有没有闭包在引用它(运行时可达性)。
- 没有返回内部函数 → 外层上下文执行完即销毁 → 无闭包
- 返回了内部函数且被外部持有 → 引擎标记该作用域链为“活跃” → 变量对象驻留
- 若外部引用被清空(如 counter = null),且无其他引用,变量对象才可能被 GC 回收
闭包不是bug,是设计必然
JavaScript 把函数当作一等公民,允许它被传递、存储、延迟执行;又采用静态词法作用域,不依赖调用位置判断变量归属。这两个特性叠加,闭包就不可避免地出现——它不是为了“封装私有变量”而发明的补丁,而是这套执行模型下最诚实的表现。
理解这一点,就不会纠结“怎么避免闭包”,而会思考“要不要让这个变量一直活着”。真正需要警惕的,从来不是闭包本身,而是意外延长变量生命周期导致的内存泄漏。











