There is one feature of JavaScript that might cause a few headaches to developers, related to loops and scoping.
JavaScript的一项功能可能会引起开发人员的麻烦,与循环和作用域有关。
Take this example:
举个例子:
const operations = []
for (var i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
It basically iterates and for 5 times it adds a function to an array called operations. This function console logs the loop index variable i
.
它基本上进行迭代,并向数组中添加一个函数5次,称为操作。 该功能控制台记录循环索引变量i
。
Later it runs these functions.
稍后它将运行这些功能。
The expected result here should be:
这里的预期结果应该是:
0
1
2
3
4
but actually what happens is this:
但是实际上发生了什么:
5
5
5
5
5
Why is this the case? Because of the use of var
.
为什么会这样呢? 由于使用var
。
Since var
declarations are hoisted, the above code equals to
由于var
声明已吊起 ,因此上述代码等于
var i;
const operations = []
for (i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
so, in the for-of loop, i
is still visible, it’s equal to 5 and every reference to i
in the function is going to use this value.
因此,在for-of循环中, i
仍然可见,它等于5,并且函数中对i
每个引用都将使用该值。
So how should we do to make things work as we want?
那么,我们应该如何做才能使事情如我们所愿?
The simplest solution is to use let
declarations. Introduced in ES6, they are a great help in avoiding some of the weird things about var
declarations.
最简单的解决方案是使用let
声明。 它们在ES6中引入,对于避免有关var
声明的某些怪异现象提供了很大的帮助。
Changing var
to let
in the loop variable is going to work fine:
更改var
以let
循环变量可以正常工作:
const operations = []
for (let i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
Here’s the output:
这是输出:
0
1
2
3
4
How is this possible? This works because on every loop iteration i
is created as a new variable each time, and every function added to the operations
array gets its own copy of i
.
这怎么可能? 之所以operations
,是因为在每次循环迭代中,每次都会将i
创建为一个新变量,并且添加到operations
数组的每个函数都将获得自己的i
副本。
Keep in mind you cannot use const
in this case, because there would be an error as for
tries to assign a new value in the second iteration.
请记住,您不能使用const
在这种情况下,因为会有一个错误for
尝试分配在第二次迭代的新值。
Another way to solve this problem was very common in pre-ES6 code, and it is called Immediately Invoked Function Expression (IIFE).
解决此问题的另一种方法在ES6之前的代码中非常普遍,称为立即调用函数表达式 (IIFE)。
In this case you can wrap the entire function and bind i
to it. Since in this way you’re creating a function that immediately executes, you return a new function from it, so we can execute it later:
在这种情况下,您可以包装整个函数并将i
绑定到它。 由于以这种方式创建了一个立即执行的函数,因此您从该函数返回了一个新函数,因此我们以后可以执行它:
const operations = []
for (var i = 0; i < 5; i++) {
operations.push(((j) => {
return () => console.log(j)
})(i))
}
for (const operation of operations) {
operation()
}