for 循环中未声明的变量 (ES6)

Undeclared variable in a for loop (ES6)

学习ES6一周了,对这段代码的行为不太理解。

    var elements = ["alfa", "beta", "gamma"];
for ( letter of elements){
  setTimeout(function printer(){console.log(letter); }, 0);
  }

我知道 let、const 和 var 之间的区别,但这篇文章没有使用其中任何一个,而且我知道在循环中使用 let 声明时,我会在控制台中输出所有三个。但是当在循环外和循环内使用 var 时,以及在代码中不使用上述任何内容时,我得到三倍的 gamma。 我不明白伽玛来自哪里,因为没有宣布信件。代码中的执行步骤是什么,它在哪里获取数组的最后一个元素。

编辑: 问题不在于如何迭代所有元素,而在于代码为何如此运行。我知道我可以使用 let 来迭代所有元素。我想知道这个 'poorly' 编写的代码的执行步骤。为什么我得到 3 倍伽玛值?

谢谢

在循环中使用 let(而不是 var)。这是一个封闭的事情......这使得每次迭代都使用不同的值。使用 var 重用该值。并且因为它是超时,最后一次迭代会在调用超时之前覆盖。

所以 letter 设置为 'alfa',然后 'beta',然后 'gamma',然后从现在 [=31] 的同一个变量调用超时 3x =].

使用let 强制运行时为循环的每次迭代保留一个单独的变量。使用var会重用,不指定会重用一个全局变量

var elements = ["alfa", "beta", "gamma"];
for (let letter of elements) {
  setTimeout(function printer() {
    console.log(letter);
  }, 0);
}

好吧,你问的是怎么回事 - 所以让我试着详细解释一下:)

所以在你的原始代码中,你已经(隐含地)用 var 关键字声明了 letter ,然后在适当的超时间隔完成后设置 3 个函数到 运行 .当浏览器开始执行这些函数,并查找它被要求打印出来的 letter 变量的值时,它看到它具有值 "gamma" - 因为现在循环已经很久了完成,因此循环变量 letter 的最终值仍然来自循环的最后一次迭代。

上面的其他人已经对此进行了解释,但您仍然可能(并且应该!)想知道 - "why does it work differently with let?"。

答案是,当我天真地说浏览器 "looks up the value of the letter variable" 时,我略微掩饰了一些东西。因为它只能在一个范围内这样做。当你在 JS 中使用 var 声明一个变量时,它在它所在的整个函数的范围内(或者如果它不在一个范围内则为全局范围) - 显然只有一个,所以它的值会随着循环的进行而被覆盖。

但是当你使用 let 时,你可能很清楚,它的范围是它所在的 { } 块——它可以是一个函数,也可以是一个循环,[= if 语句的 76=],或者甚至是你选择在 { } 中放置代码块的任何其他地方(这是合法的 JS 语法)。这经常有用,但似乎不会影响您的代码。

您可能不知道的是 letfor 循环中用于初始化变量时以一种特殊的方式表现 - 这是它的作用域是循环块本身,此外(这是这里的关键),它被有效地重新声明为每个循环迭代。也就是说,就好像您写的是循环 "longhand",在 3 次迭代中的每一次周围都有一个 {..} 块,并且在每个迭代的顶部都有一个 let letter = ... 声明块。

这就是为什么在 header 中使用 let 时循环会输出您想要的结果的原因 - 因为在循环结束很久之后,浏览器会查找letter 它应该打印出来的变量,它正在查找的变量的特定 "instance" 仅绑定到它在其中声明的特定循环迭代。所以这就是为什么值不是 over-ridden,就像你使用 var 时一样 - 回调函数通过 different "instance" 传递给 setTimeout "close" letter 变量,而不仅仅是一个用于整个循环的变量。

万一这看起来仍然像 "magic" - 它实际上不是,尽管它是 let 关键字的语法便利,我认为它是专门引入的,因为它对于开发人员对像您这样的循环没有按照人们的预期进行挠头。但是它相对容易修复(当你知道发生了什么!)即使没有 let 可用,像这样:

for (var letter of elements) {
    var thisLetter = letter;
    (function(letter) {
        setTimeout(function printer(){console.log(letter); }, 0);
    })(thisLetter);
}

(function(..) {..})(..) 称为 "Immediately Invoked Function Expression",或简称 IIFE - 它只是定义一个函数然后立即执行它。因为函数创建了一个新的作用域,所以这样做意味着 letter 变量在传递给 setTimeout 的每个函数的新作用域中 - 这正是您使用 let 时发生的情况。而且,即使 let 已经使这个特殊的结构过时(除非你仍然必须支持非 ES6 浏览器*),我想说了解 IIFEs 和闭包是非常有用的,因为它们总是出现在 Javascript。网上有很多关于它们的 well-written 文章(这是我了解这些东西的唯一原因,我才开始学习编码不到 2 年)。

*在有人试图迂腐之前(有多少开发人员不是 :p),是的,我知道这个示例具有 for..of,因此无论如何在 ES6 环境中只有 运行s。但是完全相同的问题可以 - 并且确实 - 提出 for..in 和常规 for 循环。