Solution:
The problem is that the variable in each anonymous function, i
, is bound to the same variable as the function.
Let’s talk about ES6.
ECMAScript 6 introduces let
and const
keywords. These keywords are different from var
-based variables. In a loop that uses a let
-based index, for example, every iteration of the loop will be accompanied by a new variable called i
, This allows your code to work as expected. There are many resources available, but 2ality’s block scoping post is a great resource.
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
Attention, Edge 9-IE11, Edge before Edge 14, and Edge before Edge 14 support let
it but get the above wrong. They don’t create a brand new i
every time so all the functions listed above would log three, just like if we used var
). Edge 14 finally does it right.
Solution to ES5.1: forEach
With the relatively widespread availability of the Array.prototype.forEach
function (in 2015), it’s worth noting that in those situations involving iteration primarily over an array of values, .forEach()
provides a clean, natural way to get a distinct closure for every iteration. This means that if you have an array of values (DOM references or objects), you don’t need to set up callbacks for each element.
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
Each invocation of the callback function that is used with the loop .forEach
will have its own closure. The array element for that step of the iteration is the parameter that is passed to that handler. It won’t interfere with any other calls made at the same step of the iteration if it’s used in an Asynchronous Callback.
You can use the same $.each()
function if you are working in jQuery.
Closures are the solution
You want to bind each variable in a function to a distinct, unchanging value outside the function.
var funcs = [];
function createfunc(i) {
return function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
JavaScript does not have a block scope, but only function scope. By wrapping the function in a new function you can ensure that “i” is the same value as it was intended.
Try this:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Edit(2014)
@Aust’s recent answer regarding using .bind
seems to me the best way to do it right now. You can also use _.partial
for lo-dash/underscore when you don’t want to mess around with bind
‘s thisArg
.
Another way that hasn’t been mentioned yet is the use of Function.prototype.bind
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function(x) {
console.log('My value: ' + x);
}.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
UPDATE
@squint & @mekdev pointed out that you can get more performance by creating the function outside of the loop and binding the results inside the loop.
function log(x) {
console.log('My value: ' + x);
}
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = log.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Leave a Reply