'setTimeOut' calls in JavaScript 'for' loops, why do they fail?
You are correct that lexical scoping is the cause of this behavior. When the timer functions run (which will be after the currently running code completes), they attempt to resolve i
and they must look up the scope chain to find it. Because of lexical scoping, i
exists only once in the scope chain (one scope higher than the timer functions) and, at that point, i
is 6
because, at that point, the loop has terminated.
The var
keyword causes variables in JavaScript to have either function or Global scope (based on where that declaration is). In your code, var i
causes the i
variable to exist Globally (because your code is not inside of a function) and each timer function must resolve that same, single i
when they eventually run. Since the timer functions won't run until the loop is done, i
is at its last value (6) that the loop caused it to be.
Change var i
to let i
to create block scope for i
to solve the problem.
let
creates block scope for the variable. Upon each iteration of the loop, you enter the loop block again and a separate scope is created for i
that each timer function gets to itself.
for (let i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i); }, 1000*i);
}
Let me explain with your code:
for (var i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i); }, 1000*i);
}
At the moment the function setTimeout()
was triggered, the variable of i will be equal 1,2,3,4,5 as you expected, till the value of i increases to 6 and stop the for-looping.
var i = 1;
setTimeout(function() { console.log(i); }, 1000*1);
i++;
setTimeout(function() { console.log(i); }, 1000*2);
i++;
setTimeout(function() { console.log(i); }, 1000*3);
i++;
setTimeout(function() { console.log(i); }, 1000*4);
i++;
setTimeout(function() { console.log(i); }, 1000*5);
i++;
// Now i = 6 and stop the for-looping.
After a period of time, the callback of timeout
will be triggered, and do console log value of i. Have a look above, as I said, the value of i was 6 already.
console.log(i) // i = 6 already.
console.log(i) // i = 6 already.
console.log(i) // i = 6 already.
console.log(i) // i = 6 already.
console.log(i) // i = 6 already.
The cause is the lack of ECMAScript 5: block scope
. (var i = 1;i <=5 ;i++)
will create a variable that will exist in the whole function, and it can be modified by the function in the local scope or closure scope. That's the reason why we have let
in ECMAScript 6.
It can be fixed easily by changing var
to let
:
for (let i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i); }, 1000*i);
}