Ниже фрагмент JavaScript — объясните, почему код выводит именно такие значения, какие механизмы языка участвовали, и предложите две разные корректные версии, сохраняющие замыкание по каждой итерации цикла: for (var i = 0; i
Что выводит код for (var i = 0; i < 3; i++) { setTimeout(function(){ console.log(i); }, 100) } и почему
На практике этот код выведет: 3 3 3 (три раза число 3).
Почему так происходит (ключевые механизмы JS):
var — функциональная (или глобальная) область видимости. Переменная i одна на всю функцию (нет отдельной переменной для каждой итерации).setTimeout — асинхронная: колбэки ставятся в очередь и выполняются позже (после завершения текущного кода / цикла). То есть все три setTimeout регистрируются, цикл доходит до конца и i становится 3 до того, как любой из колбэков выполнится.Замыкание (closure) захватывает ссылку на переменную i, а не «значение в момент создания». Когда колбэки выполняются, они читают текущее значение i — это 3.
Две правильные версии, которые сохраняют отдельное замыкание на каждую итерацию
1) Использовать let (ES6) — для for каждая итерация получает свою лексическую привязку i: for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 100); } Вывод: 0, 1, 2. Причина: let создаёт новую привязку переменной для каждой итерации цикла, замыкание захватывает именно эту привязку.
2) Использовать IIFE (немедленно вызываемое функцию) и передавать i как параметр — захватить значение в локальной переменной функции: for (var i = 0; i < 3; i++) { (function (j) { setTimeout(function () { console.log(j); }, 100); })(i); } Вывод: 0, 1, 2. Причина: IIFE получает текущее значение i в параметре j; замыкание внутри setTimeout ссылается на j, уникальную для каждой итерации.
(Ещё альтернативы: использовать Function.prototype.bind: setTimeout(console.log.bind(null, i), 100), либо передавать аргументы в setTimeout: setTimeout(function(x){ console.log(x); }, 100, i).)
Кратко: проблема связана с областями видимости (var) и тем, что замыкание хранит ссылку на переменную, а колбэки выполняются позже. Решения — создать для каждой итерации отдельную переменную (let, IIFE, bind, аргументы setTimeout и т. п.).
Что выводит код
На практике этот код выведет:for (var i = 0; i < 3; i++) {
setTimeout(function(){ console.log(i); }, 100)
}
и почему
3
3
3
(три раза число 3).
Почему так происходит (ключевые механизмы JS):
var — функциональная (или глобальная) область видимости. Переменная i одна на всю функцию (нет отдельной переменной для каждой итерации).setTimeout — асинхронная: колбэки ставятся в очередь и выполняются позже (после завершения текущного кода / цикла). То есть все три setTimeout регистрируются, цикл доходит до конца и i становится 3 до того, как любой из колбэков выполнится.Замыкание (closure) захватывает ссылку на переменную i, а не «значение в момент создания». Когда колбэки выполняются, они читают текущее значение i — это 3.Две правильные версии, которые сохраняют отдельное замыкание на каждую итерацию
1) Использовать let (ES6) — для for каждая итерация получает свою лексическую привязку i:
for (let i = 0; i < 3; i++) {
setTimeout(function () { console.log(i); }, 100);
}
Вывод: 0, 1, 2. Причина: let создаёт новую привязку переменной для каждой итерации цикла, замыкание захватывает именно эту привязку.
2) Использовать IIFE (немедленно вызываемое функцию) и передавать i как параметр — захватить значение в локальной переменной функции:
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(function () { console.log(j); }, 100);
})(i);
}
Вывод: 0, 1, 2. Причина: IIFE получает текущее значение i в параметре j; замыкание внутри setTimeout ссылается на j, уникальную для каждой итерации.
(Ещё альтернативы: использовать Function.prototype.bind: setTimeout(console.log.bind(null, i), 100), либо передавать аргументы в setTimeout: setTimeout(function(x){ console.log(x); }, 100, i).)
Кратко: проблема связана с областями видимости (var) и тем, что замыкание хранит ссылку на переменную, а колбэки выполняются позже. Решения — создать для каждой итерации отдельную переменную (let, IIFE, bind, аргументы setTimeout и т. п.).