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



From 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块的一半内。

i,用var声明,被吊起。变量不会自动将其作用域绑定到内部函数;除非内部函数显式具有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调用,并且该函数将使用一个新的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);
}

因此,您可以看到在这种情况下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(或任何互联网爆炸器支持(而使用转译器,您将看到转译版本是

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

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

最新更新