这个问题在这里已有答案:
我已经学习ES6一个星期了,我不明白这段代码的行为。
var elements = ["alfa", "beta", "gamma"];
for ( letter of elements){
setTimeout(function printer(){console.log(letter); }, 0);
}
我知道let,const和var之间的区别,但是这篇文章并没有使用任何这些,我知道在循环中使用let声明时我会在控制台中输出所有三个。但是当使用var时,无论是在循环内部还是在内部,当在代码中没有使用上述任何内容时,我得到三次gamma。我不明白伽玛的来源,因为没有宣布信件。在代码中执行的步骤是什么,以及它在何处获取数组的最后一个元素。
编辑:问题不是如何迭代所有元素,而是为什么代码的行为方式。我知道我可以使用let来迭代所有元素。我想知道执行这个“糟糕”的代码的步骤。我为什么要获得3倍伽玛?
谢谢
在你的循环中使用let
(而不是var
)。这是一个封闭的东西......这使得每次迭代都使用不同的值。使用var
重用该值。并且因为它是超时,所以在调用超时之前覆盖最后一次迭代。
因此letter
设置为'alfa',然后是'beta',然后是'gamma',然后从现在为'gamma'的相同变量调用超时3倍。
使用let
强制运行时为循环的每次迭代保留单独的变量。使用var
将重用,而不指定将重用全局变量。
var elements = ["alfa", "beta", "gamma"];
for (let letter of elements) {
setTimeout(function printer() {
console.log(letter);
}, 0);
}
那么你确实问过发生了什么 - 所以让我试着详细解释一下:)
因此,在原始代码中,您(隐式)使用letter
关键字声明了var
,然后在适当的超时间隔完成后设置3个函数来运行。当浏览器执行这些函数,并查找要求打印出来的letter
变量的值时,它会看到它的值为"gamma"
- 因为到现在为止循环很长,所以循环变量letter
从循环的最后一次迭代中仍然有它的最终值。
上面的其他人已经解释过这一点,但你仍然可能(并且应该!)想知道 - “为什么它与let
的工作方式不同?”。
答案是,当我无辜地说浏览器“查找letter
变量的值”时,我略微掩饰了一些东西。因为它只能在范围内这样做。当你使用var
在JS中声明一个变量时,它在整个函数的范围内(如果它不在一个范围内的全局范围) - 显然它只有一个,所以它的值得到随着循环的进行而被覆盖。
但是当你使用let
时,你可能知道,它的范围是{ }
块,它在里面 - 这可能是一个函数,但也是一个循环,一个if
语句的主体,甚至你选择的任何其他地方在{ }
中放入一段代码(这是合法的JS语法)。这很常用,但似乎不会影响您的代码。
您可能不知道的是let
在用于初始化for
循环中的变量时以特殊方式运行 - 这就是它的范围是循环块本身,而且(这是这里的关键),它每次循环迭代都有效地重新声明。也就是说,就好像你已经写了“循环”循环,在3次迭代中每次都有一个{..}
块,并且在每个块的顶部都有一个let letter = ...
声明。
这就是当你在标题中使用let
时循环输出你想要它的原因 - 因为当循环结束很久之后,浏览器会查找它应该打印出来的letter
变量的值,特定的“实例” “它所查找的变量仅限于它所声明的特定循环迭代。所以这就是为什么值不会被覆盖,就像你使用var
时那样 - 回调函数传递给setTimeout
”close“在letter
变量的不同“实例”上,而不是整个循环中的一个。
如果它仍然看起来像“魔术” - 它实际上不是,虽然它是let
关键字的语法便利,我认为这是特别介绍的,因为开发人员如此常见地抓住你的头像你不做的循环人们的期望。但即使没有let
,它也很容易修复(当你知道发生了什么!),如下所示:
for (var letter of elements) {
var thisLetter = letter;
(function(letter) {
setTimeout(function printer(){console.log(letter); }, 0);
})(thisLetter);
}
(function(..) {..})(..)
被称为“立即调用函数表达式”,或简称IIFE - 它只是定义一个函数然后立即执行它。因为函数创建了一个新的范围,所以这样做意味着letter
变量在传递给setTimeout
的每个函数的新范围内 - 这正是使用let
时发生的情况。而且,即使let
已经使这个特殊的结构过时(除非你仍然需要支持非ES6浏览器*),我会说理解IIFE和闭包是非常有用的,因为它们总是在Javascript中出现。网上有大量关于它们的精心撰写的文章(这是我知道这些东西的唯一原因,我开始学习不到两年前的编码)。
*在有人试图迂腐之前(以及有多少开发人员不是:p),是的我知道这个例子具有for..of
,因此无论如何只能在ES6环境中运行。但完全相同的问题可以 - 而且确实 - 提出for..in
和常规for
循环。