Variable capturing quirks
Take a quick second to guess what the output of the following snippet is:
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); }
For those unfamiliar, setTimeout
will try to execute a function after a certain number of milliseconds (though waiting for anything else to stop running).
Ready? Take a look:
10 10 10 10 10 10 10 10 10 10
Many JavaScript developers are intimately familiar with this behavior, but if you’re surprised, you’re certainly not alone. Most people expect the output to be
0 1 2 3 4 5 6 7 8 9
Remember what we mentioned earlier about variable capturing? Every function expression we pass to setTimeout
actually refers to the same i
from the same scope.
Let’s take a minute to consider that means. setTimeout
will run a function after some number of milliseconds, but only after the for
loop has stopped executing; By the time the for
loop has stopped executing, the value of i
is 10
. So each time the given function gets called, it will print out 10
!
A common work around is to use an IIFE - an Immediately Invoked Function Expression - to capture i
at each iteration:
for (var i = 0; i < 10; i++) { // capture the current state of 'i' // by invoking a function with its current value (function(i) { setTimeout(function() { console.log(i); }, 100 * i); })(i); }
This odd-looking pattern is actually pretty common. The i
in the parameter list actually shadows the i
declared in the for
loop, but since we named them the same, we didn’t have to modify the loop body too much.
Please login to continue.