for循环中的未声明变量(ES6)[重复]

问题描述 投票:-2回答:2

这个问题在这里已有答案:

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

谢谢

javascript for-loop ecmascript-6 variable-declaration
2个回答
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);
}

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循环。

© www.soinside.com 2019 - 2024. All rights reserved.