‘setTimeOut’调用JavaScript’for’循环,为什么它们会失败?

'setTimeOut' calls in JavaScript 'for' loops, why do they fail?

本问题已经有最佳答案,请猛点这里访问。

让我澄清一下我的问题。我不是问如何使以下代码工作。我知道你可以使用let关键字或iffe来捕获它自己的i值。我只需要澄清如何在以下代码中访问值i。我阅读了以下博客文章,了解以下代码是如何工作的。博客文章

1
2
3
for (var i = 1; i <= 5; i++) {
    setTimeout(function() { console.log(i); }, 1000*i);     // 6 6 6 6 6
}

作者声称代码不起作用,因为我们将变量i作为引用而不是值传递。也就是说,我们不是每次迭代提供i的值,而是将变量作为参考提供给setTimeout中的回调。实际上,当循环终止并且回调触发时,我们将引用变量i,它将是6.这是如何工作的?

这是我的理解。我的理解是,当执行循环时,我们不会将任何内容"传递"到setTimeout函数的回调中。我们只是设置异步调用。当闭包回调函数执行时,它们会根据词法作用域规则查找变量i。也就是说,在范围内的闭包是回调结束了,在这种情况下再次,因为它是在for循环完成之后完成的。

它是哪一个,函数是否根据在每次迭代时作为引用传递的变量或者因为词法作用域而将i的值解析为6?


你是正确的,词法范围是这种行为的原因。当计时器功能运行时(将在当前运行的代码完成之后),它们会尝试解析i并且他们必须查找范围链以找到它。由于词法作用域,i在作用域链中只存在一次(一个作用域高于计时器函数),并且此时i6,因为此时循环已终止。

var关键字使JavaScript中的变量具有函数或全局范围(基于声明的位置)。在您的代码中,var i导致i变量全局存在(因为您的代码不在函数内),并且每个定时器函数必须在它们最终运行时解析相同的单个i。由于定时器函数在循环完成之前不会运行,因此i处于循环导致它的最后一个值(6)。

var i更改为let i以创建i的块范围以解决问题。

let为变量创建块范围。在循环的每次迭代中,您再次进入循环块,并为i创建一个单独的范围,每个计时器函数都会自行获取。

1
2
3
for (let i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}


让我用你的代码解释一下:

1
2
3
for (var i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}

在触发函数setTimeout()时,i的变量将等于您预期的1,2,3,4,5,直到i的值增加到6并停止for循环。

1
2
3
4
5
6
7
8
9
10
11
12
   var i = 1;
   setTimeout(function() { console.log(i); }, 1000*1);
   i++;
   setTimeout(function() { console.log(i); }, 1000*2);
   i++;
   setTimeout(function() { console.log(i); }, 1000*3);
   i++;
   setTimeout(function() { console.log(i); }, 1000*4);
   i++;
   setTimeout(function() { console.log(i); }, 1000*5);
   i++;
   // Now i = 6 and stop the for-looping.

一段时间后,将触发timeout的回调,并执行i的控制台日志值。看看上面,正如我所说,我的价值已经是6。

1
2
3
4
5
    console.log(i) // i = 6 already.
    console.log(i) // i = 6 already.
    console.log(i) // i = 6 already.
    console.log(i) // i = 6 already.
    console.log(i) // i = 6 already.

原因是缺乏ECMAScript 5:block scope(var i = 1;i <=5 ;i++)将创建一个将存在于整个函数中的变量,并且可以通过本地范围或闭包范围中的函数进行修改。这就是为什么我们在ECMAScript&nbsp; 6中有let的原因。

通过将var更改为let可以轻松修复:

1
2
3
for (let i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}