JavaScript for循环中let块级作用域为何表现与预期不符
两段JS代码输出差异原因及for循环中let的作用域规则
两段代码输出不一致的核心原因是:for循环头部用let声明的迭代变量,有专门的作用域绑定规则,和普通块级作用域中声明再自增的let变量行为完全不同,你写的第二段代码并不是for循环逻辑的等价实现。
代码2的运行逻辑
代码2里的i是外层代码块独有的一份变量绑定,两个setTimeout的回调闭包,捕获的都是这同一个i:
- 同步代码按顺序执行:声明
i=0,注册第一个定时器回调,接着执行i++把i的值改成1,再注册第二个定时器回调。 - 等1秒后定时器触发时,同步代码早就执行完毕,此时两个回调去读捕获的
i,值都是1,所以最终输出1 1,这是普通块级作用域+闭包的正常表现。
for循环中let的特殊绑定规则
ES6规范对for循环头部的let声明做了专门设计:
- 循环初始化时会创建一个基础的迭代变量绑定,但每一轮循环启动时,都会在当前轮的循环体块级作用域里,生成一个全新的、独立的迭代变量绑定,把上一轮结束后迭代变量的值拷贝给这个新绑定。
- 循环体内的代码(包括闭包),捕获的都是当前轮专属的这个独立绑定,不会和其他轮的变量共享。
对应到你的代码1:第一轮循环捕获的是绑定值为0的专属i,第二轮捕获的是绑定值为1的专属i,等定时器触发时各自读取自己捕获的绑定值,自然输出0 1。
代码1的真正等价块级写法
如果要用普通块作用域模拟for循环let的行为,应该写成下面这样,就能得到和代码1一致的输出:
{ // 循环初始化的外层i let i = 0 { // 第一轮专属的i绑定 const loopI = i setTimeout(()=>{console.log(loopI)},1000) } i++ { // 第二轮专属的i绑定 const loopI = i setTimeout(()=>{console.log(loopI)},1000) } }
补充一个常见踩坑:如果把迭代变量的let声明写在for循环外面,就会失去每轮独立绑定的特性,输出和代码2一致:
let i; for(i=0;i<=1;i++){ setTimeout(()=>{console.log(i)},1000) } // 输出:1 1
内容的提问来源于stack exchange,提问作者jack
相关产品推荐
相关产品推荐

