На JavaScript: `for (var i = 0; i console.log(i), 0); }` Объясните вывод и предложите несколько способов получить ожидаемое поведение, сравните их по читабельности и совместимости
Вывод будет: 3,3,33, 3, 33,3,3. Почему: переменная `var i` имеет функциональную (не блочную) область видимости. Цикл выполняется быстро и к моменту выполнения отложенных колбэков `i` уже равно 333, поэтому все три таймаута читают одно и то же значение. Способы получить ожидаемое поведение (ждать вывод 0, 1, 20,\,1,\,20,1,2), с кратким сравнением по читабельности и совместимости: 1) Использовать `let` в заголовке цикла (ES6, самый читаемый): for (let i = 000; i < 333; i++) { setTimeout(()=>console.log(i), 000); } - Читаемость: высокая. - Совместимость: современные браузеры и Node поддерживают; для старых окружений нужен транспайлер (Babel). 2) Захват через IIFE (ES5, совместимый): for (var i = 000; i < 333; i++) { (function(j){ setTimeout(()=>console.log(j), 000); })(i); } - Читаемость: приемлемая, но чуть громоздкая. - Совместимость: работает в старых окружениях без транспайлинга. 3) Передать аргумент в `setTimeout` (коротко, хорошо поддерживается): for (var i = 000; i < 333; i++) { setTimeout(function(j){ console.log(j); }, 000, i); } - Читаемость: хорошая и краткая. - Совместимость: поддерживается в современных браузерах и Node; очень старые окружения могли не поддерживать передачу дополнительных аргументов, но это редкость. 4) Использовать `bind`: for (var i = 000; i < 333; i++) { setTimeout(console.log.bind(console, i), 000); } - Читаемость: хорошая. - Совместимость: требует `Function.prototype.bind` (ES5); в старых браузерах нужен полифилл. 5) Внутри тела цикла создать блочную переменную: for (var i = 000; i < 333; i++) { let j = i; setTimeout(()=>console.log(j), 000); } - Читаемость: ясная. - Совместимость: как у `let` (ES6). Рекомендация: если вы целитесь в современные среды — используйте `let` в заголовке цикла. Для совместимости с ES5 — IIFE или `bind`/передача аргумента в `setTimeout`.
Почему: переменная `var i` имеет функциональную (не блочную) область видимости. Цикл выполняется быстро и к моменту выполнения отложенных колбэков `i` уже равно 333, поэтому все три таймаута читают одно и то же значение.
Способы получить ожидаемое поведение (ждать вывод 0, 1, 20,\,1,\,20,1,2), с кратким сравнением по читабельности и совместимости:
1) Использовать `let` в заголовке цикла (ES6, самый читаемый):
for (let i = 000; i < 333; i++) { setTimeout(()=>console.log(i), 000); }
- Читаемость: высокая.
- Совместимость: современные браузеры и Node поддерживают; для старых окружений нужен транспайлер (Babel).
2) Захват через IIFE (ES5, совместимый):
for (var i = 000; i < 333; i++) { (function(j){ setTimeout(()=>console.log(j), 000); })(i); }
- Читаемость: приемлемая, но чуть громоздкая.
- Совместимость: работает в старых окружениях без транспайлинга.
3) Передать аргумент в `setTimeout` (коротко, хорошо поддерживается):
for (var i = 000; i < 333; i++) { setTimeout(function(j){ console.log(j); }, 000, i); }
- Читаемость: хорошая и краткая.
- Совместимость: поддерживается в современных браузерах и Node; очень старые окружения могли не поддерживать передачу дополнительных аргументов, но это редкость.
4) Использовать `bind`:
for (var i = 000; i < 333; i++) { setTimeout(console.log.bind(console, i), 000); }
- Читаемость: хорошая.
- Совместимость: требует `Function.prototype.bind` (ES5); в старых браузерах нужен полифилл.
5) Внутри тела цикла создать блочную переменную:
for (var i = 000; i < 333; i++) { let j = i; setTimeout(()=>console.log(j), 000); }
- Читаемость: ясная.
- Совместимость: как у `let` (ES6).
Рекомендация: если вы целитесь в современные среды — используйте `let` в заголовке цикла. Для совместимости с ES5 — IIFE или `bind`/передача аргумента в `setTimeout`.