Почему привязки let и var ведут себя по-разному с помощью функции setTimeout? [дубликат]
этот вопрос уже есть ответ здесь:
этот код журналы 6
, 6 раз:
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
но этот код...
(function timer() {
for (let i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
... регистрирует следующее результат:
0
1
2
3
4
5
почему?
потому что let
привязывает к внутренней области каждый элемент по-разному и var
сохраняет последнее значение i
?
2 ответов
С var
у вас есть область функции и только одна общая привязка для всех итераций цикла-т. е. i
в каждом setTimeout обратный вызов означает тот же переменная наконец-то равно 6 после завершения итерации цикла.
С let
у вас есть объем блока и при использовании в for
петли вы получаете новую привязку для каждой итерации, т. е. i
в каждом обратном вызове setTimeout значит другое переменная, каждая из которых имеет разное значение: первая-0, следующая-1 и т. д.
значит так:
(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}
})();
эквивалентно этому, используя только var:
(function timer() {
for (var j = 0; j <= 5; j++) {
(function () {
var i = j;
setTimeout(function clog() { console.log(i); }, i * 1000);
}());
}
})();
использование немедленно вызванного выражения функции для использования области функции аналогично тому, как область блока работает в Примере с let
.
это может быть написано короче без использования j
имя, но, возможно, это не будет как ясно:
(function timer() {
for (var i = 0; i <= 5; i++) {
(function (i) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}(i));
}
})();
и еще короче с функциями стрелки:
(() => {
for (var i = 0; i <= 5; i++) {
(i => setTimeout(() => console.log(i), i * 1000))(i);
}
})();
(но если вы можете использовать функции со стрелками, нет причин использовать var
.)
вот как Вавилон.js переводит ваш пример с let
для работы в средах, где let
не имеется:
"use strict";
(function timer() {
var _loop = function (i) {
setTimeout(function clog() {
console.log(i);
}, i * 1000);
};
for (var i = 0; i <= 5; i++) {
_loop(i);
}
})();
спасибо Майкл Гири для размещения ссылки на Babel.js в комментариях. См. ссылку в комментарии для живой демонстрации, где вы можете измените что-нибудь в коде и наблюдайте, как перевод происходит немедленно. Интересно посмотреть, как переводятся и другие функции ES6.
технически это то, как @rsp объясняет в своем отличном ответе. Вот как мне нравится понимать, что вещи работают под капотом. Для первого блока кода используется var
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
вы можете себе представить, что компилятор идет так внутри цикла for
setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec
setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec
и так далее
С i
объявляется с помощью var
, когда clog
вызывается, компилятор находит переменную i
в ближайшем функциональном блоке, который является timer
и так как мы уже дошли до конца for
цикл i
содержит значение 6 и execute clog
. Это объясняет, что 6 регистрируется шесть раз.