Кейс (JavaScript): почему следующий код печатает 10 десять раз, а не 0..9, и как исправить: for (var i = 0; i console.log(i), 0); }; обсудите разные решения и их последствия
Коротко — потому что `var` даёт одну общую переменную `i` для всей функции: цикл завершился и `i` стало равным 10 \,10\,10, а отложенные колбэки используют ту же самую переменную, поэтому все выводят 10 \,10\,10. Объяснение (с примерами и решениями) Исходный код: `for (var i = 000; i < 101010; i++) { setTimeout(() => console.log(i), 000); }` Проблема: колбэки захватывают ссылку на одну `i`, которая после цикла = 10 \,10\,10. Варианты исправления 1) Использовать `let` (простой и рекомендуемый) `for (let i = 000; i < 101010; i++) { setTimeout(() => console.log(i), 000); }` Пояснение: `let` даёт новую привязку `i` на каждую итерацию цикла, колбэк получит правильное значение 0…9 \,0\ldots9\,0…9. Совместимость: ES6+ (поддерживается в современных браузерах и Node). 2) Замыкание через IIFE (совместимо со старыми средами) `for (var i = 000; i < 101010; i++) { (function(j){ setTimeout(function(){ console.log(j); }, 000); })(i); }` Пояснение: в IIFE значение `i` копируется в параметр `j`, колбэк закрывает `j`. 3) Передать значение как аргумент в `setTimeout` `for (var i = 000; i < 101010; i++) { setTimeout(console.log, 000, i); }` Пояснение: многие реализации `setTimeout` (браузеры, Node) позволяют дополнительные аргументы — они будут переданы в колбэк как значения. 4) Использовать `bind` `for (var i = 000; i < 101010; i++) { setTimeout(console.log.bind(null, i), 000); }` Пояснение: создаётся функция с захваченным значением `i`. 5) Создать замыкание через стрелочную функцию `for (var i = 000; i < 101010; i++) { setTimeout(((j)=>()=>console.log(j))(i), 000); }` Последствия и замечания - `let` — самый чистый и читабельный метод; требует ES6. - IIFE работает в старых средах, но делает код громоздким. - `setTimeout(fn, delay, ...args)` — кратко, но старые очень старые среды могли не поддерживать дополнительные аргументы (в реальности в браузерах/Node давно поддерживается). - `bind` создаёт новую функцию; может менять `this` (в примерах мы ставим `null`). - Производительность: различия незначительны для обычного использования. - В других конструкциях (например, `forEach`) проблема не возникает, если итератор даёт локальную переменную. Рекомендация: используйте `let` в цикле (`for (let i = ... )`) или, если нужна совместимость со старыми движками, используйте IIFE или передачу аргумента в `setTimeout`.
Объяснение (с примерами и решениями)
Исходный код:
`for (var i = 000; i < 101010; i++) { setTimeout(() => console.log(i), 000); }`
Проблема: колбэки захватывают ссылку на одну `i`, которая после цикла = 10 \,10\,10.
Варианты исправления
1) Использовать `let` (простой и рекомендуемый)
`for (let i = 000; i < 101010; i++) { setTimeout(() => console.log(i), 000); }`
Пояснение: `let` даёт новую привязку `i` на каждую итерацию цикла, колбэк получит правильное значение 0…9 \,0\ldots9\,0…9.
Совместимость: ES6+ (поддерживается в современных браузерах и Node).
2) Замыкание через IIFE (совместимо со старыми средами)
`for (var i = 000; i < 101010; i++) { (function(j){ setTimeout(function(){ console.log(j); }, 000); })(i); }`
Пояснение: в IIFE значение `i` копируется в параметр `j`, колбэк закрывает `j`.
3) Передать значение как аргумент в `setTimeout`
`for (var i = 000; i < 101010; i++) { setTimeout(console.log, 000, i); }`
Пояснение: многие реализации `setTimeout` (браузеры, Node) позволяют дополнительные аргументы — они будут переданы в колбэк как значения.
4) Использовать `bind`
`for (var i = 000; i < 101010; i++) { setTimeout(console.log.bind(null, i), 000); }`
Пояснение: создаётся функция с захваченным значением `i`.
5) Создать замыкание через стрелочную функцию
`for (var i = 000; i < 101010; i++) { setTimeout(((j)=>()=>console.log(j))(i), 000); }`
Последствия и замечания
- `let` — самый чистый и читабельный метод; требует ES6.
- IIFE работает в старых средах, но делает код громоздким.
- `setTimeout(fn, delay, ...args)` — кратко, но старые очень старые среды могли не поддерживать дополнительные аргументы (в реальности в браузерах/Node давно поддерживается).
- `bind` создаёт новую функцию; может менять `this` (в примерах мы ставим `null`).
- Производительность: различия незначительны для обычного использования.
- В других конструкциях (например, `forEach`) проблема не возникает, если итератор даёт локальную переменную.
Рекомендация: используйте `let` в цикле (`for (let i = ... )`) или, если нужна совместимость со старыми движками, используйте IIFE или передачу аргумента в `setTimeout`.