缘起

下面是js考察闭包的经典问题:

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

要使输出为6,一般这样构建闭包:

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

ES6引入块级作用域之后,可以这样实现:

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

误解

由于自己有一点C语言基础,很容易将上述ES6实现理解为:

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

在C中,int定义在for内括号还是for之外是一样的,为什么这里输出了10?

  1. 块级作用域是以{}标识的,for循环应该生成十个{}子块作用域
  2. 每个子块{}中关联的是相应的i,即每次循环的i值
  3. let放在for语句内和for语句外部应该都在十个{}子块的外部,都初始化一次,为什么会产生不一致?

    原因

    根本上在于,let放到for的括号内js在底层机制处理上另有标准相关标准
    ES6写法分解为伪代码应该如下:
 (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)
    };
};
  • 每一次循环都单独初始化了一次let定义的变量值
  • 这似乎与“经验不符”,产生这样的误解根本还是JS语言的丰富性和语言最初设计的“灵活”,这造成了后面标准升级polyfill时不可避免地留下此类的坑。
  • 学习JS务必要清楚落实到标准上,一定要求甚解,理解什么是语法糖什么是“公理性”语法