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

JavaScript: Understanding let scope inside for loop

请考虑以下代码段-

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

在这种情况下,setTimeout 中的日志将根据 for 循环的每次迭代包含变量 i 的值,即日志如下

1
2
3
4
5

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

内创建了一个范围

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

这样的 for 循环:

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

和这样做是一样的:

{
  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);
}

变量在 for 循环的范围内声明和赋值五次,每个实例都与其他实例完全分开。

一般说明

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

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

因此,每次循环都会有一个新变量 i 与所有其他变量分开,每个新变量都用前一个变量的值初始化,然后由 i++for 循环声明中。

对于你的 ES6 代码:

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

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

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

需要两个IIFE(立即调用的函数表达式)来模拟这个。外层隔离了 var i,这样它就不会从 for 循环中泄漏出来,而内层为 for 循环的每次调用提供了一个单独的范围和单独的变量。 return ji = (function(j) {...})(i) 是为了显示循环的下一次迭代如何受到循环变量修改的影响。

希望这能说明 let 对于 ES6 中的 for 循环有多么有用,以及当您 need/want 此功能时它会替换多少其他代码。

现在回答您的具体问题

For this, I have read explanations over the Internet like - let creates a variable declaration for each loop which is block level declaration. So basically it creates a scope within { }

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

Also, variable i is declared, initialized and incremented outside the loop block i.e. curly braces in the for loop statement.

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

So, in each iteration isn’t the same variable i is incremented and utilized?

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

How exactly does let creates a variable declaration for each loop and has value of previous iteration?

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

修改循环变量时的特例

letfor 循环中也有一个特例。如果您在循环中分配 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
   }
}

这将创建输出:

1
2
4
5

所以,这会跳过循环的 3 迭代,因为当 i === 2 时,我们将其递增到 3,然后 for 循环执行其 i++ 迭代并且将它提高到 4,有效地跳过 3 迭代。

在javascript中你可以通过var或let来定义一个变量。定义为 let 的变量不 refined.Lets 考虑示例代码片段

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript let</h2>

<p id="demo"></p>

<script>
var i = 3;
for (var i = 0; i < 10; i++) {
  // some statements
  document.getElementById("demo").innerHTML += i;
}
document.getElementById("demo").innerHTML += i;
</script>

</body>
</html>

在此代码中,输出将是 --> 012345678910 相比之下,循环中带let的变量不会在循环外重新声明变量。

<!DOCTYPE html>
<html>
<body>

   <h2>JavaScript let</h2>

    <p id="demo"></p>

    <script>
let i = 5;
for (let i = 0; i < 10; i++) {
  // some statements
  document.getElementById("demo").innerHTML += i;
}
document.getElementById("demo").innerHTML += i;
</script>

</body>
</html>

以上代码的输出将是 --> 01234567895 这里循环外的变量保持不变,执行内层循环,循环结束后,全局变量的值仍然是5。基本上是按照作用域来分隔变量。