这个问题在这里已有答案:
让我澄清一下我的问题。我不是问如何使以下代码工作。我知道你可以使用let关键字或iffe来捕获自己的i
值。我只需要澄清如何在以下代码中访问值i
。我阅读了以下博客文章,了解以下代码是如何工作的。 Blog post
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
,他们必须查找scope chain才能找到它。由于词法范围,i
在范围链中只存在一次(一个范围高于计时器函数),并且在那时,i
是6
,因为在那时,循环已经终止。
var
关键字使JavaScript中的变量具有函数或全局范围(基于声明的位置)。在你的代码中,var i
导致i
变量全局存在(因为你的代码不在函数内),并且每个定时器函数必须在它们最终运行时解析相同的单个i
。由于定时器函数在循环完成之前不会运行,因此i
处于循环导致它的最后一个值(6)。
将var i
改为let i
to create block scope为i
解决问题。
let
为变量创建块范围。在循环的每次迭代时,再次进入循环块,并为i
创建一个单独的范围,每个计时器函数都会自动进入。
for (let i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i); }, 1000*i);
}
让我用你的代码解释一下:
for (var i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i); }, 1000*i);
}
在触发函数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.
一段时间后,将触发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.
原因是缺乏ECMAScript 5:block scope
。 (var i = 1;i <=5 ;i++)
将创建一个将存在于整个函数中的变量,并且可以通过本地范围或闭包范围中的函数进行修改。这就是我们在ECMAScript 6中使用let
的原因。
它可以通过将var
更改为let
来轻松修复:
for (let i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i); }, 1000*i);
}