Почему IIFE необходим для создания новой области?

От вас не знают, 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 ответа

Решение

Важной частью 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 i или параметр i (таким образом, определяя новый i привязаны к сфере действия внутренней функции), i будет продолжать ссылаться на лапы i во внешнем объеме.

Например, вы можете сделать то, о чем думали, если хотите:

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

(Третий аргумент setTimeout это то, с чем функция, второй аргумент, будет вызываться)

Это означает, что timer будет вызван с i как во время итерации, и функция будет использовать новый i, привязанный к области действия функции, инициализируется через параметр.

Это довольно плохая идея - лучше использовать const а также let, которые имеют область видимости блока, а не область функции, и лучше не скрывать внешние переменные.

Этот вид 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 Explorer), вы увидите, что транспортируемая версия

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

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

Который невероятно похож на предыдущую версию кода

Другие вопросы по тегам