Результат этого кода — три строки с числом 3 вконсолибудетнапечатанопоочереди3,3,3в консоли будет напечатано по очереди 3, 3, 3вконсолибудетнапечатанопоочереди3,3,3.
Почему так происходит короткокороткокоротко:
var i — функциональная неблочнаяне блочнаянеблочная переменная. В цикле создаётся одна переменная i, которую все три функции в setTimeout «замыкают» онихранятссылкунаоднуитужепеременнуюони хранят ссылку на одну и ту же переменнуюонихранятссылкунаоднуитужепеременную.setTimeout с задержкой 100 ms выполняет колбэки уже после завершения цикла. К тому моменту цикл закончился и i стало равно 3. Поэтому каждая из функций при вызове читает текущее значение i = 3.
Пояснение с точки зрения событийного цикла: setTimeout планирует колбэк в очередь событий; выполнение колбэков произойдёт позже, когда стек свободен — а к тому времени переменная i уже изменилась.
Три разных способа исправить спримерами,плюспреимущества/недостаткис примерами, плюс преимущества/недостаткиспримерами,плюспреимущества/недостатки:
1) Использовать let ES6ES6ES6, блочная область видимости
for (let i = 0; i < 3; i++) { setTimeoutfunction()console.log(i);,100function() { console.log(i); }, 100function()console.log(i);,100; }
Почему работает: при каждой итерации для i создаётся новая блочная привязка, колбэк «замыкает» своё собственное i. Выведет 0, 1, 2.
Плюсы: лаконично, современно, понятное поведение.
Минусы: требует ES6 встарыхокруженияхбезтранспиляции—несовместимов старых окружениях без транспиляции — несовместимовстарыхокруженияхбезтранспиляции—несовместимо.
2) Создать замыкание через IIFE подходитдлястарогоJSподходит для старого JSподходитдлястарогоJS
for (var i = 0; i < 3; i++) { function(j)setTimeout(function()console.log(j);,100);function(j) { setTimeout(function() { console.log(j); }, 100); }function(j)setTimeout(function()console.log(j);,100);iii; }
Почему работает: IIFE получает текущее значение i в параметр j и создаёт новую область, которую замыкает колбэк. Выведет 0, 1, 2.
Плюсы: работает в старых средах без ES6.
Минусы: более многословно, может выглядеть менее современно.
3) Передать значение в setTimeout илииспользоватьbindили использовать bindилииспользоватьbind
Вариант A — дополнительные аргументы setTimeout:
for (var i = 0; i < 3; i++) { setTimeoutfunction(j)console.log(j);,100,ifunction(j) { console.log(j); }, 100, ifunction(j)console.log(j);,100,i; }
Вариант B — bind:
for (var i = 0; i < 3; i++) { setTimeoutconsole.log.bind(null,i),100console.log.bind(null, i), 100console.log.bind(null,i),100; }
Почему работает: setTimeout позволяет передать аргументы в колбэк илиbindфиксируетаргументили bind фиксирует аргументилиbindфиксируетаргумент — передаётся текущее значение i, которое становится независимым от переменной i. Выведет 0, 1, 2.
Плюсы: коротко, явно фиксирует значение.
Минусы: передача дополнительных аргументов setTimeout исторически поддерживается большинством сред, но может выглядеть необычно; bind может менять контекст/параметры внекоторыхслучаяхнеподходит,еслинуженспецифичныйthisв некоторых случаях не подходит, если нужен специфичный thisвнекоторыхслучаяхнеподходит,еслинуженспецифичныйthis.
Короткая заметка: аналогично let сработает и const вfor−of/forциклеconstдаётновуюпривязкунакаждуюитерацию,нозначениенеможетбытьпереназначеновтелеитерациив for-of/for цикле const даёт новую привязку на каждую итерацию, но значение не может быть переназначено в теле итерациивfor−of/forциклеconstдаётновуюпривязкунакаждуюитерацию,нозначениенеможетбытьпереназначеновтелеитерации.
Вывод: в современных проектах самый простой и читаемый способ — использовать let илиconstили constилиconst, в старых — IIFE или передачу аргументов/ bind.
Результат этого кода — три строки с числом 3 вконсолибудетнапечатанопоочереди3,3,3в консоли будет напечатано по очереди 3, 3, 3вконсолибудетнапечатанопоочереди3,3,3.
Почему так происходит короткокороткокоротко:
var i — функциональная неблочнаяне блочнаянеблочная переменная. В цикле создаётся одна переменная i, которую все три функции в setTimeout «замыкают» онихранятссылкунаоднуитужепеременнуюони хранят ссылку на одну и ту же переменнуюонихранятссылкунаоднуитужепеременную.setTimeout с задержкой 100 ms выполняет колбэки уже после завершения цикла. К тому моменту цикл закончился и i стало равно 3. Поэтому каждая из функций при вызове читает текущее значение i = 3.Пояснение с точки зрения событийного цикла: setTimeout планирует колбэк в очередь событий; выполнение колбэков произойдёт позже, когда стек свободен — а к тому времени переменная i уже изменилась.
Три разных способа исправить спримерами,плюспреимущества/недостаткис примерами, плюс преимущества/недостаткиспримерами,плюспреимущества/недостатки:
1) Использовать let ES6ES6ES6, блочная область видимости
for (let i = 0; i < 3; i++) {setTimeoutfunction()console.log(i);,100function() {
console.log(i);
}, 100function()console.log(i);,100;
}
Почему работает: при каждой итерации для i создаётся новая блочная привязка, колбэк «замыкает» своё собственное i. Выведет 0, 1, 2.
Плюсы: лаконично, современно, понятное поведение.
Минусы: требует ES6 встарыхокруженияхбезтранспиляции—несовместимов старых окружениях без транспиляции — несовместимовстарыхокруженияхбезтранспиляции—несовместимо.
2) Создать замыкание через IIFE подходитдлястарогоJSподходит для старого JSподходитдлястарогоJS
for (var i = 0; i < 3; i++) {function(j)setTimeout(function()console.log(j);,100);function(j) {
setTimeout(function() {
console.log(j);
}, 100);
}function(j)setTimeout(function()console.log(j);,100);iii;
}
Почему работает: IIFE получает текущее значение i в параметр j и создаёт новую область, которую замыкает колбэк. Выведет 0, 1, 2.
Плюсы: работает в старых средах без ES6.
Минусы: более многословно, может выглядеть менее современно.
3) Передать значение в setTimeout илииспользоватьbindили использовать bindилииспользоватьbind Вариант A — дополнительные аргументы setTimeout:
for (var i = 0; i < 3; i++) {setTimeoutfunction(j)console.log(j);,100,ifunction(j) {
console.log(j);
}, 100, ifunction(j)console.log(j);,100,i;
}
Вариант B — bind:
for (var i = 0; i < 3; i++) {setTimeoutconsole.log.bind(null,i),100console.log.bind(null, i), 100console.log.bind(null,i),100;
}
Почему работает: setTimeout позволяет передать аргументы в колбэк илиbindфиксируетаргументили bind фиксирует аргументилиbindфиксируетаргумент — передаётся текущее значение i, которое становится независимым от переменной i. Выведет 0, 1, 2.
Плюсы: коротко, явно фиксирует значение.
Минусы: передача дополнительных аргументов setTimeout исторически поддерживается большинством сред, но может выглядеть необычно; bind может менять контекст/параметры внекоторыхслучаяхнеподходит,еслинуженспецифичныйthisв некоторых случаях не подходит, если нужен специфичный thisвнекоторыхслучаяхнеподходит,еслинуженспецифичныйthis.
Короткая заметка: аналогично let сработает и const вfor−of/forциклеconstдаётновуюпривязкунакаждуюитерацию,нозначениенеможетбытьпереназначеновтелеитерациив for-of/for цикле const даёт новую привязку на каждую итерацию, но значение не может быть переназначено в теле итерациивfor−of/forциклеconstдаётновуюпривязкунакаждуюитерацию,нозначениенеможетбытьпереназначеновтелеитерации.
Вывод: в современных проектах самый простой и читаемый способ — использовать let илиconstили constилиconst, в старых — IIFE или передачу аргументов/ bind.