JavaScript闭包逻辑疑问:为何闭包引用变量取到重赋值后的值而非声明时的值?
闭包的核心运行逻辑:捕获的是变量绑定,而非静态值快照
你之前的认知偏差本质上是把闭包的作用错误等同于“函数声明时,把外部作用域的变量值拍一张静态快照存起来”,但JavaScript的闭包从设计之初就不是基于这个逻辑实现的。
闭包的实际实现规则
JS中的函数在被创建时,会将自身所在的外层词法作用域的引用存储到函数内部隐藏的[[Scope]]属性中。这个存储逻辑完全不关心变量在函数声明那一刻存的是什么值,它存的是变量和作用域内存位置的绑定关系——只要这个变量没有被垃圾回收销毁,后续函数不管在什么时机读取这个变量,都会顺着这个绑定关系,取到变量当前存储的最新值。
示例逐行执行拆解
我们把你给出的代码按执行顺序拆成步骤,就能清晰解释为什么最终输出100:
- 执行
f()时,首先在f的函数作用域中创建名为x的变量绑定,给它赋初始值1 - 声明箭头函数
g的时候,JS会给g的[[Scope]]属性挂上当前f作用域的引用。此时g和x的关联,本质是“我要读取的x,就是当前f作用域下定义的那个叫x的变量”,完全没有把x当时的初始值1存成g的私有数据 - 接着执行
x = 100,这行操作是直接修改f作用域中那个x绑定上存储的值,把原来的1覆盖成100,整个过程g存储的作用域绑定没有发生任何变化 - 最后调用
g()时,g顺着自己存的作用域引用找到对应的x绑定,读取它当前的最新值,自然就输出100,而非声明g时的1
如何实现真正的“声明时存值快照”
如果要达成“函数创建时就固定捕获当时的变量值”的效果,需要主动把变量值和外部作用域的绑定脱钩,常见实现是通过函数传参生成独立的块级作用域:
function f() { let x = 1; const g = (function(currentX) { // currentX是IIFE作用域下的独立变量,和外部的x绑定没有关联 return () => console.log(currentX); })(x); x = 100; g(); // 此处才会输出1 } f()
这种作用域绑定的机制,也是经典的“循环绑定事件全取最后一个值”bug的核心成因:如果循环时用
var声明循环变量,所有事件处理函数会共享同一个函数作用域下的var变量绑定,等用户点击触发事件时,读到的已经是循环走完后的变量最终值。ES6引入let的块级作用域后,每次循环迭代都会生成独立的x绑定,每个事件函数捕获的是对应轮次的独立绑定,这个问题才会自然消失。
内容的提问来源于stack exchange,提问作者Moti
相关产品推荐
相关产品推荐

