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

时间:2018-09-09 19:54:55

标签: javascript for-loop ecmascript-6 variable-declaration

我已经学习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倍?

谢谢

2 个答案:

答案 0 :(得分: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);
}

答案 1 :(得分:0)

您确实问过发生了什么-所以让我尝试详细解释:)

因此,在您的原始代码中,已(暗含)使用letter关键字声明了var,然后设置了3个函数以在适当的超时间隔完成后运行。当浏览器执行这些功能并查找要求打印的letter变量的值时,就会看到它具有值"gamma",因为到目前为止循环很长,因为完成,因此循环变量letter的最后一个值仍然是循环的最后一次迭代。

上面其他人已经解释了这一点,但是您仍然可能(并且应该!)想知道-“为什么它与let的工作方式不同?”。

答案是,当我无辜地说浏览器“查找letter变量的值”时,我略微掩饰了一些东西。因为它只能在一个范围内这样做。当您使用var在JS中声明变量时,它在整个函数的范围之内(如果不在其中,则为全局范围)-显然只有一个,因此它的值会随着循环的进行而被覆盖。

但是,您可能知道,当您使用let时,它位于{ }块的内部-可能是一个函数,也可能是一个循环,{ {1}}语句,甚至您选择在if内放置代码块的任何其他位置(这是合法的JS语法)。这通常很有用,但似乎不会影响您的代码。

您可能不知道的是,{ }在用于初始化let循环中的变量时会以一种特殊的方式工作-这是因为它仅限于循环块本身,并且而且(这是这里的关键),在每次循环迭代中都有效地重新声明了它。就是说,就像您写的那样,您正在循环遍历,在三个迭代中的每个迭代周围有一个for块,并且在每个块的顶部有一个{..}声明。

这就是为什么在标头中使用let letter = ...时循环将您想要的内容输出的原因-因为循环结束很长时间后,浏览器会查找let变量的值它应该被打印出来,它正在查找的变量的特定“实例”仅绑定到声明了该变量的特定循环迭代中。因此,这就是为什么不覆盖该值的原因,就像您使用时那样letter-回调函数通过var变量的不同“实例”传递给setTimeout“关闭”,而不是整个循环中的一个。

万一这看起来仍然像是“魔术”-确实不是,尽管letter关键字在语法上很方便,我相信之所以引入是因为关键字对于开发人员来说很常见像你这样的循环不按照人们的期望去做。但是即使没有let,也很容易修复(当您知道发生了什么!),就像这样:

let

for (var letter of elements) { var thisLetter = letter; (function(letter) { setTimeout(function printer(){console.log(letter); }, 0); })(thisLetter); } 被称为“立即调用函数表达式”,简称IIFE-它简单地定义了一个函数,然后立即执行它。因为函数创建了新作用域,所以这样做意味着(function(..) {..})(..)变量在传递给letter的每个函数中都处于新作用域中-这正是使用setTimeout时发生的情况。而且,即使let已使这种特殊构造过时了(除非您仍然必须支持非ES6浏览器*),我还是要说了解IIFE和闭包非常有用,因为它们一直在出现。在Javascript中。网上有很多关于它们的写得很好的文章(这是我知道这些东西的唯一原因,不到两年前我才开始学习编码。)

*在有人尝试学步之前(还有多少开发人员不是:p),是的,我知道这个示例具有let功能,因此无论如何都只能在ES6环境中运行。但是,完全相同的问题可以而且确实存在for..of和常规for..in循环。