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

Hug*_*rea 8 javascript settimeout

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

for (var i = 1; i <= 5; i++) {
    setTimeout(function() { console.log(i); }, 1000*i);     // 6 6 6 6 6
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Sco*_*cus 17

你是正确的,词法范围是这种行为的原因.当计时器功能运行时,它们会尝试解析i,并且必须查找范围链才能找到它.因为词法作用域的,i只存在于范围链(比定时器功能较高的一个范围),并且在该点一次,i6因为环路已经终止在该点.

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

更改var ilet i 创建块范围i解决这个问题.

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

for (let i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}
Run Code Online (Sandbox Code Playgroud)


Kai*_*Kai 5

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

for (var i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}
Run Code Online (Sandbox Code Playgroud)

setTimeout()触发函数的那一刻,i的变量将等于你预期的1,2,3,4,5,直到i的值增加到6并停止for循环.

   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.
Run Code Online (Sandbox Code Playgroud)

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

    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.
Run Code Online (Sandbox Code Playgroud)

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

它可以通过更改varlet:

for (let i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}
Run Code Online (Sandbox Code Playgroud)