why is IIFE needed to create a new scope?

大憨熊 提交于 2019-12-22 11:16:23

问题


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?


回答1:


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.




回答2:


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.




回答3:


This sort of IIFE

for (var i=1; i<=5; i++) {
    (function(){
        var j = i;
        setTimeout( function timer(){
            console.log( j );
        }, j*1000 );
    })();
}

is often written like

for (var i=1; i<=5; i++) {
    (function(j){
        setTimeout( function timer(){
            console.log( j );
        }, j*1000 );
    })(i);
}

so, you can see that "captured" value is i in this case

You can do the same without IIFE

for (var i=1; i<=5; i++) {
    function timer(j) {
        setTimeout(function() {
            console.log(j);
        }, j * 1000 );
    }
    timer(i);
}

of course, this is equivalent of

function timer(j) {
    setTimeout(function() {
        console.log(j);
    }, j * 1000 );
}

for (var i=1; i<=5; i++) {
    timer(i);
}

if using ES2015+, you can use let

for (let i=1; i<=5; i++) {
    setTimeout( function timer(){
        console.log( i );
    }, i*1000 );
}

Now, if you use a transpiler because you need to support ES5 (or whatever internet exploder supports) you'll see that the transpiled version is

var _loop = function _loop(i) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
};

for (var i = 1; i <= 5; i++) {
    _loop(i);
}

Which looks incredibly like the previous version of the code



来源:https://stackoverflow.com/questions/50615610/why-is-iife-needed-to-create-a-new-scope

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!