闭包内变量解析依赖词法作用域和变量提升,但提升发生于声明阶段、闭包生效于执行阶段;闭包保留对外部变量的引用而非值快照,其生命周期由引用关系维持,且不改变提升规则或绕过暂时性死区。

闭包内的变量解析,本质上依赖于 JavaScript 的词法作用域和预解析(变量提升)机制,但两者作用层级不同:变量提升发生在**声明阶段**,而闭包生效在**执行阶段**;闭包不改变变量提升规则,但它让被提升后仍“存活”的变量持续可访问。
变量提升只发生在当前作用域内
每个函数体在执行前都会经历独立的预解析:所有 var 声明和 function 声明 会被提升到该函数作用域顶部,仅声明(赋初值 undefined),不执行赋值。这意味着:
- 闭包内部函数(如返回的 inner)自身也有预解析,它会提升自己作用域里的 var 变量;
- 但它访问的外层变量(比如来自 outer 的 var a = 1)——这个 a 是在 outer 函数执行时被提升并初始化的,inner 并不参与 outer 的预解析,只是通过作用域链引用它;
- 如果 outer 中用 let/const 声明变量,则不存在提升,inner 在访问时直接按块级作用域规则查找,进入“暂时性死区”则报错。
闭包保留的是变量的“绑定”,不是值的快照
闭包捕获的是对外部变量的**引用**,而非复制一份值。因此:
- 多个闭包共享同一外部变量时,修改其中一个会影响其他闭包的读取结果;
- 循环中用 var 创建闭包(如 for 循环里返回函数),由于变量 i 被提升且共用一个绑定,所有闭包最终都指向循环结束后的 i 值(如 3);
- 改用 let 声明循环变量,则每次迭代生成独立绑定,每个闭包拿到各自迭代时的 i 值——这不是闭包变了,而是 let 提供了块级绑定,作用域链上查到的是不同内存位置。
闭包变量的生命周期由引用关系决定
普通函数执行完,其私有作用域通常被垃圾回收;但闭包存在时:
- 只要闭包函数还被引用(如赋给全局变量、作为事件回调等),它所依赖的外层变量就不会被释放;
- 这些变量实际存储在堆内存中,作用域链维持着对它们的强引用;
- 即使 outer 已执行完毕,inner 仍能通过闭包访问 outer 中提升过的变量(比如 var count),因为该变量绑定未断开。
常见误区澄清
很多人误以为“闭包导致变量提升失效”或“闭包让变量提前初始化”,其实并非如此:
- var 声明依然会被提升——只是提升发生在 outer 执行时,inner 访问的是已提升并可能已赋值的变量;
- 闭包不绕过暂时性死区——若 outer 中用 let x,inner 在 x 声明前访问会报 ReferenceError,作用域链不会跳过 TDZ;
- 函数表达式不被提升——如果 outer 中写 var fn = function(){...},fn 变量名被提升(值为 undefined),但函数体不会;inner 若在 fn 赋值前调用它,会得到 TypeError。











