【问题标题】:JavaScript: Understanding let scope inside for loop [duplicate]JavaScript:了解 for 循环内的 let 范围 [重复]
【发布时间】:2020-03-28 22:00:21
【问题描述】:

请考虑下面的sn-p-

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 究竟是如何为每个循环创建一个变量声明并具有上一次迭代的值的?

【问题讨论】:

  • 块范围的let 在这种情况下意味着i 将是超出该循环的undefined,而对于var,它将是它的最后一个值。

标签: javascript for-loop ecmascript-6 scope let


【解决方案1】:

一般说明

当您在 for 循环结构中使用 let 时,会为每次调用循环创建一个新变量 i,其范围仅限于循环块(在环形)。

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

因此,每次循环都会有一个新变量 i 与所有其他变量分开,并且每个新变量都使用前一个变量的值进行初始化,然后由 @ 中的 i++ 处理987654334@ 循环声明。

对于你的 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 j 和 i = (function(j) {...})(i) 是为了展示循环的下一次迭代如何受到循环变量修改的影响。

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

现在解决您的具体问题

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

内创建了一个范围

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

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

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

那么,在每次迭代中,不是同一个变量 i 都被递增和利用了吗?

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

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

这不是let 做的。这是 ES6+ JS 解释器中的 for 循环逻辑。这是for 循环的特殊行为,该循环具有用let 声明的索引初始化程序。所以,这是let和for的组合行为,但真正的逻辑是解释器执行for循环的方式。

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

let 在for 循环中还有一个特殊情况。如果您在循环中分配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 迭代。

【讨论】:

  • 谢谢你,但我认为只有一件小事值得一提。您不需要专门的 IIFE 来完成此操作,只需使用内部函数:ibb.co/bgpjnvh 或者更好地清理一下:ibb.co/8BT832C
  • 发生的情况是因为var 的作用域是函数,当每次迭代都调用一个新的内部函数时,会在内存中为内部函数创建一个新的内部变量,这就是您现在看到的原因正确的数字。
  • 而没有内部函数的 var(没有内部函数的初始示例),setTimeout 中的回调总是获取到它在内存中的父 var i 的引用,而不是 VALUE。
  • 而且由于引用不断更新,最后当 setTimeout 运行时,for 循环已经完成,setTimeout 吐出引用,此时引用是最后一个数字。
  • @Mar - 首先,你的例子有点过时了,因为你使用的是箭头函数,如果你有箭头函数,你也可以只使用 let 而没有做额外的功能。其次,您只是替换命名函数,然后调用它们而不是使用 IIFE - 这是完全相同的概念,只是代码行更多。而且,这些都不再需要了,因为我们可以使用let。
【解决方案2】:

这样的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 循环的范围内被声明和赋值五次,每个实例完全独立于其他实例。

【讨论】:

    【解决方案3】:

    在 javascript 中,您可以通过 var 或 let 定义变量。定义为 let 的变量没有细化。让我们考虑一个示例代码 sn-p

    <!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。它基本上是根据变量的范围来分隔变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-12
      • 2012-06-12
      • 1970-01-01
      • 2016-11-02
      • 1970-01-01
      • 2018-12-28
      • 2023-03-31
      相关资源
      最近更新 更多