为什么IIFE需要创建一个新的范围?

时间:2018-05-31 02:00:14

标签: javascript closures lexical-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一样工作吗?在我看来,他们都有一个function声明,其中包含一个新变量j,不会创建一个新的词法范围,其中包含i的特定设置?

3 个答案:

答案 0 :(得分:4)

IIFE的重要部分是它立即运行 ;在title_pattern = re.compile(r'.*<title>(.*?)</title>(.*)') 更改之前,它会读取其值并将其放入新变量中。您的其他示例中的i函数 - i - 不会立即运行,并且它在新变量中的值是function timer()已经更改后的值。

此外,在ES6中,您可以i而不是let i = …,如果没有IIFE或var i = …,它将正常工作:

j

因为for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i * 1000); }具有块范围而不是函数范围,并且在let循环的初始化部分中声明的变量计为在for块的一半内。

答案 1 :(得分:1)

i声明的

var被悬挂。变量不会自动将其范围绑定到内部函数;除非内部函数明确地具有var i或参数i(因此定义了绑定到内部函数范围的新i ),{{1将继续引用外部范围中已提升的i

例如,如果您愿意,可以按照以下方式执行您的想法:

&#13;
&#13;
i
&#13;
&#13;
&#13;

for (var i=1; i<=5; i++) { setTimeout( function timer(i){ console.log( i ); }, i*1000, i ); }的第三个参数是函数,第二个参数,将被调用)

这意味着setTimeout将在迭代期间使用timer 进行调用,并且该函数将使用 new {{1绑定到函数范围的函数,通过参数初始化。

但这是一个非常糟糕的主意 - 更好地使用ii,它们具有块范围而不是函数范围,最好不要遮蔽外部变量。

答案 2 :(得分:0)

这种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);
}

因此,您可以看到“捕获”值在这种情况下为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爆炸支持),您将看到转换后的版本

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

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

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