JavaScript中的词法声明用于循环初始化块。

huangapple go评论50阅读模式
英文:

Lexical Declarations in Javascript for loop initialization block

问题

在MDN文档中,它提供了一个示例,其中函数在for循环的初始化块中进行了初始化。

for (
  let i = 0, getI = () => i, incrementI = () => i++;
  getI() < 3;
  incrementI()
) {
  console.log(i);
}
// 输出 0, 0, 0

它提供了以下关于为什么会出现这种情况的解释:

这会记录"0, 0, 0",因为每次循环评估中的i变量实际上是一个单独的变量,但getI和incrementI都读取和写入了i的初始绑定,而不是后来声明的内容。

对于每次迭代,在for循环体中都会获得一个新的i。初始化块中的i变量也在for循环体的范围内。

我希望有人能更好地解释这个问题,因为我对这里发生的事情有点难以理解,因为存在一个闭包,i变量属于for循环体的范围,当我在getIincrementI中添加一个调试器语句时,一切都看起来如人们所期望的那样,只是在for循环体内部似乎看不到它,并且每次迭代都记录0。

英文:

In the mdn docs it gives an example in which functions are initialized in the for loop initialization block

for (
  let i = 0, getI = () =&gt; i, incrementI = () =&gt; i++;
  getI() &lt; 3;
  incrementI()
) {
  console.log(i);
}
// Logs 0, 0, 0

It gives the following explanation of why this occurs:
> This logs "0, 0, 0", because the i variable in each loop evaluation is actually a separate variable, but getI and incrementI both read and write the initial binding of i, not what was subsequently declared.

For each iteration we get a new i in the for loop body. The i variable in the initialization block is also in the scope of the for loop body.

I am hoping someone can explain this a bit better than what mdn did, I am having a bit of a hard time grasping what is going on here since there is a closure here, the i variable belongs to the scope of the for loop body, and when I add a debugger statement to getI or incrementI everything looks as one might expect, just inside the for loop body it doesn't seem to see it and it logs 0 each iteration.

答案1

得分: 2

每次循环迭代都会创建一个新的作用域,其中有自己绑定的 i。此外,在循环开始迭代之前,会创建一个单独的作用域,用于初始化 igetIincrementI 变量。随后的每次迭代中,在新作用域中创建的新绑定都会初始化为前一个作用域中创建的变量的值。

在循环开始执行之前,你的函数最初在初始作用域中创建,具有对在该初始作用域中创建的 i 的可见性,因此这是你的函数更新/读取的 i 版本,也是循环最终能够终止的原因。

然而,你的 for 循环体可以访问在执行该特定迭代的新作用域中定义的 i,该作用域中的 i0,因为增量表达式不会更新该特定 i 绑定,它只会更新在所有迭代开始时创建的初始绑定。

为了形象化说明,你可以将你的循环展开为以下内容,前两次迭代如下,其中 i_0i_1 表示不同迭代阶段的 i 变量:

// 初始作用域,在迭代发生之前创建
let i, incrementI, getI;
i = 0;
incrementI = () => i++;
getI = () => i;

// 第一次迭代作用域(为新迭代创建 `i`、`incrementI` 和 `getI` 的新绑定)
let i_0, incrementI_0, getI_0;
i_0 = i;
incrementI_0 = incrementI;
getI_0 = getI;

if (getI_0() < 3)
  console.log(i_0);

// 第二次迭代作用域(为新迭代创建 `i`、`incrementI` 和 `getI` 的新绑定)
let i_1, incrementI_1, getI_1; // 为新迭代创建新声明
i_1 = i_0; // 0,因为 i_0 是 0
incrementI_1 = incrementI_0;
getI_1 = getI_0;

incrementI_1(); // 增加 i_0,而不是 i_1。i_0 现在是 1

if (getI_1() < 3) // 检查 i_0(现在是 1)是否小于 3。
  console.log(i_1); // 记录 i_1,仍然是 0
英文:

Each iteration of your loop creates a new scope that has its own binding for i. In addition, before your loop begins iterating, a separate scope is created that initializes your i, getI, and incrementI variables. For each iteration that follows, the new bindings that are created in the new scopes are initialized to the values of the variables created in the previous scope created.

Your functions are initially created in the initial scope before your loop beings executing and have visibility over the i created in that initial scope, and so that's the version of i that your functions update/read, and is also why the loop is eventually able to terminate.

Your for-loop body however has access to the i defined in the new scope created at that particular iteration of the loop that’s executing, which is 0 as the incrementer expression does not update that particular i binding, it only updates the initial binding created at the beginning of all the iterations.

To put things into perspective, you could think of your loop flattening out to something like below for the first two iterations, where i_0 and i_1 represent your i variable at different iteration stages:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// Initial scope, created before iterations occur
let i, incrementI, getI;
i = 0;
incrementI = () =&gt; i++;
getI = () =&gt; i;

// First iteration scope (new bindings for `i`, `incrementI` and `getI` are created. 
let i_0, incrementI_0, getI_0;
i_0 = i;
incrementI_0 = incrementI;
getI_0 = getI;

if(getI_0() &lt; 3)
  console.log(i_0);


// Second iteration scope (new bindings for `i`, `incrementI` and `getI` are created)
let i_1, incrementI_1, getI_1; // new declarations are created for new iteration
i_1 = i_0; // 0, as i_0 is 0
incrementI_1 = incrementI_0;
getI_1 = getI_0;

incrementI_1(); // increments i_0, not i_1. i_0 is now 1

if(getI_1() &lt; 3) // checks i_0 (now 1) is less than 3.
  console.log(i_1); // logs i_1, still 0

<!-- end snippet -->

答案2

得分: 0

"i 变量属于 for 循环体的作用域

确实,但问题在于(正如 mdn 写的):

这是因为 getI 在每次迭代时不会重新评估 - 相反,该函数只创建一次并封闭 i 变量,该变量是在首次初始化循环时声明的。对 i 的后续更新实际上会创建名为 i 的新变量,getI 不会看到这些变化。

这是一句引用,但我可能无法更好地解释它。
你可以将其理解为:创建了3个 i 变量,而你的 getI() 只与第一个绑定在一起。"

英文:

> the i variable belongs to the scope of the for loop body

Indeed, but the thing is (as mdn wrote it):

> This is because getI is not re-evaluated on each iteration — rather, the function is created once and closes over the i variable, which refers to the variable declared when the loop was first initialized. Subsequent updates to the value of i actually create new variables called i, which getI does not see.

It's a quote but i could honestly not explain it better.
You could maybe see it as : 3 i variable are created and your getI() is only binded to the first one basically.

huangapple
  • 本文由 发表于 2023年6月9日 11:55:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76437109.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定