为什么需要 IIFE 来创建新范围?

why is IIFE needed to create a new scope?

来自You Don't Know JS

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

给予

6
6
6
6
6

但像这样使用 IIFE

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

给予

1
2
3
4
5

我的问题:为什么不

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

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

像 IIFE 示例一样工作?在我看来,它们都有一个带有新变量 jfunction 声明,这不会创建一个具有特定设置的新词法范围 i 吗?

IIFE 的重要部分是它运行立即;在 i 改变之前,它读取它的值并将其放入一个新变量中。在您的其他示例中读取 i 的函数 – function timer() – 不会立即 运行 ,并且它在新变量中输入的值是 i 之后的值改变了。

此外,在 ES6 中,你可以只 let i = … 而不是 var i = … 并且没有 IIFE 或 j:

也可以正常工作

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

因为 let 具有块作用域而不是函数作用域,并且在 for 循环的初始化部分中声明的变量算作 for 块内的一半。

var 声明的

i 被提升。变量不会自动将它们的范围绑定到内部函数;除非内部函数明确地具有 var ii 的参数(因此定义一个新的 i 绑定到内部函数 的范围), i 将继续引用外部作用域中提升的 i

例如,如果你愿意,你可以像这样做你想做的事情:

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

setTimeout 的第三个参数是调用函数的第二个参数)

这意味着 timer 将在迭代期间用 i 调用 ,函数将使用 new i,绑定到函数作用域,通过参数初始化。

虽然这是一个非常糟糕的主意 - 最好使用 constlet,它们具有块作用域而不是函数作用域,并且最好不要隐藏外部变量。

这种 IIFE

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

经常写成

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

所以,在这种情况下,您可以看到 "captured" 值为 i

你可以在没有 IIFE 的情况下做同样的事情

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

当然,这相当于

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

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

如果使用ES2015+,可以使用let

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

现在,如果你因为需要支持 ES5(或任何 internet exploder 支持的)而使用转译器,你会看到转译版本是

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

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

这看起来非常像以前版本的代码