ES6的let在for循环经典问题如何起作用?

问题

在复习ES6的块级作用域时候又遇到了for循环的经典问题,即

var a = [];
for(var i=0;i<10;i++){
    a[i] = function(){
        console.log(i);
    }
}
console.log(a[6]())  // 10

如何使得输出依次为1到9,使用ES6,一般这样做:

var a = [];
for(let i=0;i<10;i++){
    a[i] = function(){
        console.log(i);
    }
}
console.log(a[6]())  // 6

我的误解

我认为上述答案也可以写成:

var a = [];
let i;
for(i=0;i<10;i++){
    a[i] = function(){
        console.log(i);
    }
}
console.log(a[6]())   // 10

输出:10? 和想象中不一样啊?为什么不是6?

我的理由

  1. 块级作用域是以{}标识的,for循环应该生成十个{}子块作用域
  2. 每个子块{}中关联的是相应的i,即每次循环的i值
  3. let放在for语句内和for语句外部应该都在十个{}子块的外部吗?

请问我的错误出在哪里?

阅读 6k
4 个回答

感谢@ezmo和@xialeistudio的回答,这个问题最终还是在站内搜到了。https://segmentfault.com/a/11...
@冴羽 提到,for循环中的let是标准特别定义的部分,实际上以下代码:

var funcs = [];
for (let i = 0; i < 3; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0](); // 0

可以分解为:

// 伪代码
(let i = 0) {
    funcs[0] = function() {
        console.log(i)
    };
}

(let i = 1) {
    funcs[1] = function() {
        console.log(i)
    };
}

(let i = 2) {
    funcs[2] = function() {
        console.log(i)
    };
};

这与“经验”不符,这是造成错误理解的根本。从C语言角度看,for中变量定义和for外定义是一样的,因为语言本身只在变量定义位置初始化一次,但js中这个for内外的let处理是有不同的内在机制的。@ezmo的回答实际上解释了这种情况,但不了解for内let有独特处理标准还是云里雾里的,谁能想到简单的for中有这样一种语法定义?

错在理由第3点。 let放在for语句内,作用域是在for内的每次循环内;定义在for语句外,作用域是外面的{}内。

在for内let:每一次循环的i其实都是一个新的变量,JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

for的每次循环算一个作用域,放外面不行

推荐问题
宣传栏