Javascript 使用for循环解释'let'和块作用域

Javascript 使用for循环解释'let'和块作用域,javascript,ecmascript-6,Javascript,Ecmascript 6,我知道让防止重复声明是很好的 let x; let x; // error! 用let声明的变量也可以用于预期的闭包中 let i = 100; setTimeout(function () { console.log(i) }, i); // '100' after 100 ms 我有点难以理解的是let如何应用于循环。这似乎是特定于for循环的。考虑经典问题: // prints '10' 10 times for (var i = 0; i < 10; i++) { proces

我知道让防止重复声明是很好的

let x;
let x; // error!
let
声明的变量也可以用于预期的闭包中

let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms
我有点难以理解的是
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)) }
//打印“10”10次
对于(var i=0;i<10;i++){process.nextTick(=>console.log(i))}
//打印“0”到“9”
对于(设i=0;i<10;i++){process.nextTick(=>console.log(i))}
为什么在这种情况下使用
会让
起作用?在我的想象中,即使只有一个块是可见的,
for
实际上为每个迭代创建一个单独的块,
let
声明是在该块内部完成的。。。但是只有一个
let
声明来初始化该值。这只是ES6的语法糖吗?这是怎么回事


我理解
var
let
之间的区别,并在上面进行了说明。我特别感兴趣的是理解为什么不同的声明使用
for
循环会产生不同的输出。

引入块作用域和等效绑定,就像函数使用闭包创建作用域一样。我相信规范的相关部分是,注释中提到
let
声明是词汇绑定的一部分,它们都存在于词汇环境中。第节说明
var
声明附加到可变环境,而不是字典绑定

委员会也支持这一点,指出:

它通过在单个代码块的词法范围内绑定零个或多个变量来工作

建议变量绑定到块,这会改变每次迭代,需要一个新的LexicalBinding(我相信,在这一点上不是100%),而不是周围的lexicalEnvironment或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);
}
//打印“10”10次
对于(变量i=0;i<10;i++){
setTimeout(=>console.log('var',i),0);
}
//打印“0”到“9”
for(设i=0;i<10;i++){
setTimeout(=>console.log('let',i),0);
}
当然显示后者打印每个值。如果你看一下巴别塔是如何传输的,它会产生:

for(变量i=0;i<10;i++){
setTimeout(函数41;{
返回控制台日志(i);
}, 0);
}
var _loop=函数(_i){
setTimeout(函数41;{
返回控制台日志(_i);
}, 0);
};
//打印“0”到“9”
对于(var_i=0;_i<10;_i++){
_环(_i);
}
这只是ES6的语法糖吗

不,不仅仅是语法上的糖。血淋淋的细节被掩盖了

这是怎么回事

如果在
for
语句中使用
let
关键字,它将检查绑定的名称,然后

  • 使用这些名称创建一个新的词汇环境,用于a)初始化器表达式b)每次迭代(在计算增量表达式之前)
  • 将具有这些名称的所有变量的值从一个环境复制到下一个环境
(vari=0;i<10;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++;
        …
//当大括号不引入块时忽略大括号
var i;
i=0;
如果(i<10)
process.nextTick(=>console.log(i))
i++;
如果(i<10)
process.nextTick(=>console.log(i))
i++;
…
for(让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++;
          …
//使用大括号显式表示块范围,
//使用缩进控制流
{让我来;
i=0;
__状态={i};
}
{let{i}={u状态;
如果(i<10)
process.nextTick(=>console.log(i))
__状态={i};
}{let{i}={u状态;
i++;
如果(i<10)
process.nextTick(=>console.log(i))
__状态={i};
}{let{i}={u状态;
i++;
…
我找到了最好的:

在for循环的头部声明变量的var将创建一个 该变量的绑定(存储空间):

const arr = [];
for (var i=0; i < 3; i++) {
    arr.push(() => i);
}
arr.map(x => x()); // [3,3,3]
const arr=[];
对于(变量i=0;i<3;i++){
arr.push(()=>i);
}
arr.map(x=>x());//[3,3,3]
三个箭头函数的主体中的每个i都引用相同的函数 绑定,这就是它们都返回相同值的原因

如果让您声明一个变量,则会为每个循环创建一个新绑定 迭代:

const arr = [];
for (let i=0; i < 3; i++) {
    arr.push(() => i);
}

arr.map(x => x()); // [0,1,2]
const arr=[];
for(设i=0;i<3;i++){
arr.push(()=>i);
}
arr.map(x=>x());//[0,1,2]
这一次,每个i都引用一个特定迭代和 保留当时的当前值。因此 arrow函数返回一个不同的值


在你的问题中你几乎已经说过了,每次循环迭代时,
let
基本上都会重新计算。我不知道我会称之为语法糖,这只是循环是如何定义的。See的可能重复。阅读这两篇文章:,我在这里是否正确:let I=100;setTimeout(function(){console.log(I)},I);//“100”100毫秒后,我们还可以使用var i=100?@TinyGiant大多数浏览器无法运行第一个代码段,因为它们往往不支持let和arrow函数。我已经阅读了好几遍规范——我仍然不认为我完全理解,但查看Babel的输出会有所帮助。E