Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 709
user1592772 Avatar asked Oct 14 '25 14:10

user1592772


2 Answers

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.

like image 101
2 revsRy- Avatar answered Oct 17 '25 04:10

2 revsRy-


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.

like image 44
CertainPerformance Avatar answered Oct 17 '25 03:10

CertainPerformance