Exp*_*lls 31 javascript ecmascript-6
据我所知,这let
可以防止重复的声明,这很好.
let x;
let x; // error!
Run Code Online (Sandbox Code Playgroud)
声明的变量let
也可以用在可以预期的闭包中
let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms
Run Code Online (Sandbox Code Playgroud)
我有点难以理解的是如何let
应用于循环.这似乎是for
循环特有的.考虑一下经典问题:
// 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)) }
Run Code Online (Sandbox Code Playgroud)
为什么let
在这种情况下使用起作用?在我的想象中,即使只有一个块是可见的,for
实际上为每个迭代创建一个单独的块,并且let
声明在该块内完成......但是只有一个let
声明来初始化该值.这只是ES6的语法糖吗?这是怎么回事?
我理解上面和之间的差异var
,let
并在上面说明了它们.我特别感兴趣的是理解为什么不同的声明使用for
循环导致不同的输出.
Ber*_*rgi 39
这只是ES6的语法糖吗?
不,它不仅仅是语法糖.血淋淋的细节埋藏在§13.6.3.9中
CreatePerIterationEnvironment
.
这是怎么回事?
如果let
在for
语句中使用该关键字,它将检查它绑定的名称然后
你的循环语句for (var i = 0; i < 10; i++) process.nextTick(_ => console.log(i));
很简单
// 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++;
…
Run Code Online (Sandbox Code Playgroud)
虽然for (let i = 0; i < 10; i++) process.nextTick(_ => console.log(i));
"desugar"更加复杂
// 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++;
…
Run Code Online (Sandbox Code Playgroud)
我发现探索ES6的这个解释是最好的:
var声明for循环头部的变量会为该变量创建单个绑定(存储空间):
Run Code Online (Sandbox Code Playgroud)const arr = []; for (var i=0; i < 3; i++) { arr.push(() => i); } arr.map(x => x()); // [3,3,3]
三个箭头函数的主体中的每个i都指向相同的绑定,这就是它们都返回相同值的原因.
如果允许声明变量,则为每个循环迭代创建一个新绑定:
Run Code Online (Sandbox Code Playgroud)const arr = []; for (let i=0; i < 3; i++) { arr.push(() => i); } arr.map(x => x()); // [0,1,2]
这一次,每个i指的是一个特定迭代的绑定,并保留当时当前的值.因此,每个箭头函数返回一个不同的值.
let
引入了块作用域和等效绑定,就像函数创建带闭包的作用域一样.我相信规范的相关部分是13.2.1,其中注释提到let
声明是LexicalBinding的一部分,并且都存在于词法环境中.第13.2.2规定,var
声明连接到VariableEnvironment,而不是LexicalBinding.
该MDN说明支持此也,指出:
它通过在单个代码块的词法范围内绑定零个或多个变量来工作
建议变量绑定到块,这会改变需要新的LexicalBinding的每次迭代(我相信,在该点上不是100%),而不是周围的词汇环境或VariableEnvironment,它在调用期间是恒定的.
简而言之,在使用时let
,闭包位于循环体上,每次变量都不同,因此必须再次捕获.使用时var
,变量位于周围函数中,因此不需要重新闭合,并且将相同的引用传递给每次迭代.
调整您的示例以在浏览器中运行:
// 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);
}
Run Code Online (Sandbox Code Playgroud)
当然显示后者打印每个值.如果你看看Babel如何解释这个,它会产生:
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);
}
Run Code Online (Sandbox Code Playgroud)
假设Babel相当一致,那就符合我对规范的解释.
归档时间: |
|
查看次数: |
3821 次 |
最近记录: |