At some point you may have run into a problem when executing functions from within a for loop. The first time it happened to me I almost gave up programming to be a lumberjack.
Take a look at this example:
var names = ['Locke', 'Franklin', 'Smith', 'Mises'];
var logName = function(name) {
console.log(name);
};
var name;
for (var i=0; i < names.length; i++) {
name = names[i];
setTimeout(function(){
logName(name);
}, 1000);
}
In the code above, we've created an array called
names to store a list of four names and we've written a
logName function that takes in a single name as a parameter and logs it. Then we've created a for loop to iterate through our list of names, set a 1 second timeout, and then call our
logName function passing in the current name. You may think this contrived example would work, but it will actually log "Mises" four times.
The reason for this is quite simple.
logName is a closure that remembers the environment in which it was created. If you called
logName directly from the for loop it would work and print out all names. But instead we're setting a timeout, creating a closure (the callback to setTimeout), and this closure is remembering the environment. By the time our one second timeout expires, the
name variable has been set to "Mises". So it simply logs "Mises" four times.
How can we fix this? One way to fix this is by implementing another closure.
var names = ['Locke', 'Franklin', 'Smith', 'Mises'];
var logName = function(name) {
console.log(name);
};
var makeClosure = function(name) {
return function() {
logName(name);
}
};
for (var i=0; i < names.length; i++) {
var name = names[i];
setTimeout(makeClosure(name), 1000);
}
This certainly isn't beautiful, and it isn't good to create unnecessary closures. In fact, there are much better ways to accomplish this particular result, but I wanted to demonstrate the problem with closures in for loops, and how a closure can fix it. If you run the code above, you'll see that it works as expected, logging each of the four names after a one second delay. What we had to do was create a
makeClosure function that returns a closure 'remembering' the
name variable from when it was created. The closure is created instantly so it remembers the value of each
name.