Explanation of `let` and block scoping with for loops
我知道,
1 2 | let x; let x; // error! |
用
1 2 | let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms |
我有点难以理解的是
1 2 3 4 | // prints '10' 10 times for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } // prints '0' through '9' for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) } |
为什么在这种情况下使用
我了解
Is this just syntactic sugar for ES6?
不,这不仅仅是句法上的糖分。血淋淋的细节见第13.6.3.9节。
How is this working?
如果在
- 为a)初始化器表达式b)每次迭代(预先计算增量表达式)创建一个具有这些名称的新词汇环境。
- 将具有这些名称的所有变量的值从一个环境复制到下一个环境
你的循环语句
1 2 3 4 5 6 7 8 9 10 | // omitting braces when they don't introduce a block var i; i = 0; if (i < 10) process.nextTick(_ => console.log(i)) i++; if (i < 10) process.nextTick(_ => console.log(i)) i++; … |
而
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // using braces to explicitly denote block scopes, // using indentation for control flow { let i; i = 0; __status = {i}; } { let {i} = __status; if (i < 10) process.nextTick(_ => console.log(i)) __status = {i}; } { let {i} = __status; i++; if (i < 10) process.nextTick(_ => console.log(i)) __status = {i}; } { let {i} = __status; i++; … |
MDN解释也支持这一点,并指出:
It works by binding zero or more variables in the lexical scope of a single block of code
建议变量绑定到块,每次迭代都会有所不同,需要新的词汇绑定(我相信,在这一点上不是100%),而不是在调用期间保持不变的周围词汇环境或可变环境。
简而言之,当使用
调整示例以在浏览器中运行:
1 2 3 4 5 6 7 8 9 | // prints '10' 10 times for (var i = 0; i < 10; i++) { setTimeout(_ => console.log('var', i), 0); } // prints '0' through '9' for (let i = 0; i < 10; i++) { setTimeout(_ => console.log('let', i), 0); } |
当然会显示后一个值的打印。如果你看看巴别塔是如何产生这些的,它会产生:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | for (var i = 0; i < 10; i++) { setTimeout(function(_) { return console.log(i); }, 0); } var _loop = function(_i) { setTimeout(function(_) { return console.log(_i); }, 0); }; // prints '0' through '9' for (var _i = 0; _i < 10; _i++) { _loop(_i); } |
假设babel是相当一致的,这符合我对规范的解释。
我从ES6的书中找到了最好的解释:
var-declaring a variable in the head of a for loop creates a single
binding (storage space) for that variable:
1
2
3
4
5 const arr = [];
for (var i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [3,3,3]Every i in the bodies of the three arrow functions refers to the same
binding, which is why they all return the same value.If you let-declare a variable, a new binding is created for each loop
iteration:
1
2
3
4
5
6 const arr = [];
for (let i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]This time, each i refers to the binding of one specific iteration and
preserves the value that was current at that time. Therefore, each
arrow function returns a different value.