错误写法
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
为什么这样写是错的
正确写法
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
为可以这样写?
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
为可以这样写?
因为ES6之前没有块作用域
有块作用域时效果如同
就是块内代码引用的i变量均不是指向同一个变量。
而ES6则引入let关键字来标识变量位于块作用域内
当然在ES3/5下除了通过IIFE构造作用域外,还可以通过with来构造
你写成这样或许能更好明白点。
如上代码,
setTimeout
的第一个参数是一个即时执行函数表达式,也是一个闭包:该函数把循环中的
i
的值作为传参e
传入该闭包,该闭包返回另一个函数,该函数的作用域中的e
是循环时候传入的值,而不是循环结束后的i
。从作用域角度来解答,for循环中i是全局的,所以你的第一种写法i没有作用域限制会不对,第二种闭包写法把i当参数传入,会在i在局部作用完,走下一个
解决这个问题的关键:弄清楚每种写法的作用域链
闭包的形成使得外部代码块执行完毕,其变量仍然驻留在内存中。
代码块B在执行时,找不到变量i,于是沿着作用域链向上找,取到A作用域中i的值,此时内存中i值为10
理解了原理,另一种写法也是类似的,通过延长作用域链来保存每个i的值
还有一点就是,
(function(e){})(i)
(匿名函数)可以理解为其实原理就是让函数把变量复制一个副本,保存起来。
如果不以参数形式传入,使得以闭包形式保存的话,则会向上级作用域引用变量,也就是你说的i会是最后一个
One often made mistake is to use closures inside of loops, as if they were copying the value of the loop's index variable.
The above will not output the numbers 0 through 9, but will simply print the number 10 ten times.
The anonymous function keeps a reference to i. At the time console.log gets called, the for loop has already finished, and the value of i has been set to 10.
In order to get the desired behavior, it is necessary to create a copy of the value of i.
Avoiding the Reference Problem
The anonymous outer function gets called immediately with i as its first argument and will receive a copy of the value of i as its parameter e.
The anonymous function that gets passed to setTimeout now has a reference to e, whose value does not get changed by the loop.
There is another possible way of achieving this, which is to return a function from the anonymous wrapper that will then have the same behavior as the code above.
The other popular way to achieve this is to add an additional argument to the setTimeout function, which passes these arguments to the callback.
Some legacy JS environments (Internet Explorer 9 & below) do not support this.
There's yet another way to accomplish this by using .bind, which can bind a this context and arguments to function. It behaves identically to the code above
可以一看:《JavaScript闭包详解》