JavaScript:了解 for 循环内的 let 作用域

Abh*_*ari 2 javascript scope for-loop let ecmascript-6

请考虑下面的片段-

for(let i = 1; i <= 5; i++) {
   setTimeout(function(){
       console.log(i);
   },100);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,内部的日志setTimeout将包含i每个 for 循环迭代的变量值,即日志如下

1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

为此,我已经阅读了 Internet 上的解释,例如 -let为每个循环创建一个变量声明,这是块级声明。所以基本上它在{ }.

但我对这个说法有点困惑。如果let为每个循环创建一个变量声明,它不会总是1按照循环初始化语句进行初始化let i=1吗?此外,变量i在循环块外声明、初始化和递增,即for循环语句中的花括号。那么,在每次迭代中,不是i增加和使用相同的变量吗?究竟如何let为每个循环创建一个变量声明并具有前一次迭代的值?

jfr*_*d00 10

一般说明

当你像你展示的那样letfor循环结构中使用时,会为循环的i每次调用创建一个新变量,该变量的范围仅限于循环块(无法在循环外访问)。

循环的第一次迭代从for循环初始值设定项(i = 1在您的示例中)获取其值。i每次循环迭代创建的其他新变量从ifor 上一次循环调用中获取它们的值,而不是从 thei = 1中获取它们的值,这就是它们没有全部初始化为 的原因1

这样,通过每次循环有一个新的变量i是从所有其他的单独的和每一个新的一个初始化与前一个的值,然后被处理由i++for循环声明。

对于您的 ES6 代码:

for(let i = 1; i <= 5; i++) {
   setTimeout(function(){
       console.log(i);
   },100);
} 
Run Code Online (Sandbox Code Playgroud)

在 ES5 中,这将是一个本质上等效的结构。如果你真的研究过这个,它可以为你上面 ES6 代码中实际发生的事情提供非常丰富的信息:

(function() {
    for (var i = 1; i <= 5; i++) {
        i = (function(j) {
            setTimeout(function(){
                console.log(j);
            },100);
            return j;
        })(i);
    }
})();
Run Code Online (Sandbox Code Playgroud)

这需要两个 IIFE(立即调用的函数表达式)来模拟。外层将 隔离var i,使其不会泄漏到for循环之外,而内层则为for循环的每次调用提供单独的作用域和单独的变量。该return ji = (function(j) {...})(i)是展示如何循环的下一次迭代通过修改循环变量的影响。

希望这说明了ES6 中的letforfor循环非常有用,以及当您需要/想要此功能时它替换了多少其他代码。

现在回答您的具体问题

为此,我已经阅读了 Internet 上的解释,例如 - 让为每个循环创建一个变量声明,这是块级声明。所以基本上它在 { } 内创建了一个范围

let定义具有块作用域的变量(不是像 那样的函数作用域var)。而且,描述循环的{and确实定义了一个范围。}for

此外,变量 i 在循环块外(即 for 循环语句中的花括号)被声明、初始化和递增。

嗯,不完全是。该for回路是如何初始化第一的指令i为循环的第一次调用创建。let i = 1出现在循环之外的事实确实看起来有点令人困惑,但它实际上只是一个指令,说明它在i为循环的第一次调用创建第一个变量时要做什么。第一个i变量实际上并不存在于循环范围之外。

那么,在每次迭代中,i 增加和使用的变量不是同一个吗?

不会。当 ES6 遇到for带有let定义的循环时,它会为循环的每次迭代创建一个新变量。

let 究竟是如何为每个循环创建一个变量声明并具有前一次迭代的值的?

它不是let在做这个。这是forES6+ JS 解释器中的循环逻辑。对于for具有用 声明的索引初始值设定项的循环来说,这是一种特殊行为let。因此,它是letand的组合行为for,但真正的逻辑在于for解释器执行循环的方式。

修改循环变量时的特殊情况

对于letinfor循环还有一个特殊情况。如果您i在循环中分配 的值,它将更改 的特定值,i并且还会影响下一次迭代的 值i。这是一个特殊情况,但它允许您仍然操作i循环中的值。

for(let i = 1; i <= 5; i++) {
   let j = i;
   setTimeout(function(){
       console.log(j);
   },100);
   if (i === 2) {
      i++;         // bump the loop increment to skip the 3 value
   }
}
Run Code Online (Sandbox Code Playgroud)

这将创建输出:

1
2
4
5
Run Code Online (Sandbox Code Playgroud)

因此,这会跳过3循环的迭代,因为当 时i === 2,我们将其增加到 3,然后for循环进行i++迭代并将其提升到4,从而有效地跳过3迭代。


sym*_*ink 5

像这样的 for 循环:

for (let i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}
Run Code Online (Sandbox Code Playgroud)

和这样做一样:

{
  let i = 1;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 2;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 3;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 4;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 5;
  setTimeout(function() {
    console.log(i);
  }, 100);
}
Run Code Online (Sandbox Code Playgroud)

该变量在 for 循环的范围内被声明和分配了五次,并且每个实例都与其他实例完全分开。