JavaScript:了解 for 循环内的 let 范围 [重复]

Posted

技术标签:

【中文标题】JavaScript:了解 for 循环内的 let 范围 [重复]【英文标题】:JavaScript: Understanding let scope inside for loop [duplicate] 【发布时间】: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,它将是它的最后一个值。 【参考方案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 ji = (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 声明的索引初始化程序。所以,这是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 迭代。

【讨论】:

谢谢你,但我认为只有一件小事值得一提。您不需要专门的 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。它基本上是根据变量的范围来分隔变量。

【讨论】:

以上是关于JavaScript:了解 for 循环内的 let 范围 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何打破递归函数内的 for 循环并在 Javascript 中返回?

深入了解 JavaScript 中的 for 循环

深入了解 JavaScript 中的 for 循环

深入了解 JavaScript 中的 for 循环

for 循环内的 async.waterfall 转义 for 循环

第786期深入了解 JavaScript 中的 for 循环