From You Don't Know JS:
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
gives
6
6
6
6
6
but using an IIFE like so
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
gives
1
2
3
4
5
My question: why doesn't
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
var j = i;
console.log( j );
}, i*1000 );
}
or
for (var i=1; i<=5; i++) {
function timer() {
var j = i;
console.log(j);
}
setTimeout(timer, i*1000 );
}
work like the IIFE example? It seems to me they both have a function declaration with a new variable j, wouldn't that create a new lexical scope with a specific setting for i?
The important part of the IIFE is that it runs right away; before i changes, it reads its value and puts it in a new variable. The function reading i in your other examples – function timer() – does not run right away, and the value it puts in its new variable is the value of i after it’s already changed.
Also, in ES6, you can just let i = … instead of var i = … and it’ll work fine without the IIFE or j:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
because let has block scope instead of function scope and variables declared in the initialization part of for loops count as being half-inside the for’s block.
i, being declared with var, is hoisted. Variables don't automatically get their scopes bound to an inner function; unless the inner function explicitly has var i or a paramter of i (thus defining a new i bound to the scope of the inner function), i will continue to refer to the hoisted i in the outer scope.
For example, you could do what you were thinking of like this, if you wanted:
for (var i=1; i<=5; i++) {
setTimeout( function timer(i){
console.log( i );
}, i*1000, i );
}
(The third argument to setTimeout is what the function, the second argument, will be called with)
This means that timer will be called with i as it is during iteration, and the function will use a new i, bound to the scope of the function, initialized via the parameter.
It's a pretty bad idea, though - better to use const and let, which have block scope rather than function scope, and better not to shadow outer variables.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With