Search code examples
javascriptclosureslexical-scope

why is IIFE needed to create a new scope?


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?


Solution

  • 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.