在闭包上下文中 for 循环内声明的 let 变量的行为

Behavior of let variables declared inside for loop in context of closures

在下面的代码片段中,传递给 setTimeout 的函数与 script 作用域中存在的变量 i 形成一个闭包。因此该函数包含对变量 i 引用 i 的值在 i 记录到控制台之前更新为 5。输出为:5 5 5 5 5(带换行符)

script.js:

let i;
for (i = 0; i < 5; ++i) {
    setTimeout(() => {
        console.log(i);
    }, i * 1000);
}

以上部分我已经很清楚了。 但是当我在for循环中声明变量i时,i的块作用域是for循环。

for (let i = 0; i < 5; ++i) {
    setTimeout(() => {
        console.log(i);
    }, i * 1000);
}

在这个代码片段中,我希望输出与第一个片段的输出相同,因为闭包包含对变量 ireference 和值在将值记录到控制台之前,reference 应该更新为 5。但实际输出是:0 1 2 3 4(带换行符)

问题是,为什么第二个片段中的代码会这样?

是因为:

  1. 对于 for 循环的每次迭代都会在内存中创建变量 i 的新副本,并且之前的副本被垃圾收集?我不确定循环的内存管理是如何工作的。
  2. 不是存储 i 的引用,而是 i 存储在闭包中?我不认为是这样。
  3. 还有什么吗?

请帮忙说明一下。谢谢!

您的问题提到了一些实施细节。然而,这些实施细节在回答您的问题时并不重要。

您确实是对的,for 循环发生了一些特定的行为。让我们在这里看看 ECMAScript 2021 标准:

当评估带有 let 的 for 循环时,首先执行 these steps

我们对第 4、9 和 10 步特别感兴趣。第 4 步收集在 for 循环中声明的所有 constlet 变量。

第 9 步将 perIterationLets 设置为 let 变量(如果有 let 变量),否则设置为空列表。第 10 步然后调用 ForBodyEvaluation 实际 运行 循环:

这里,我们先关注一下3.e,仔细看看CreatePerIterationEnvironment:

仔细遵循调用层次结构,我们注意到 perIterationBindings 是我们之前收集的 let 个变量的列表 perIterationLets

在行 e.i、e.ii、e.iii 中,我们现在创建一个新绑定(将其视为变量所在的内存位置)并复制值将上一次迭代中具有相同名称的变量放入其中。这是一个不同的新变量,即使它具有相同的名称!

因此如果循环体内的闭包捕获当前上下文,循环变量将不会改变,因为每个循环体迭代都有自己的当前上下文.