Проблема замыканий JavaScript в циклах – как исправить

Столкнулся с странным поведением функций в циклах

Пишу код где создаю несколько функций в цикле, но они все выводят одно и то же значение вместо разных. Вот пример:

var handlers = [];
// создаём 5 функций
for (var counter = 0; counter < 5; counter++) {
  // сохраняем их в массив
  handlers[counter] = function() {
    // каждая должна показать свой номер
    console.log("Номер функции:", counter);
  };
}
for (var k = 0; k < 5; k++) {
  // запускаем все функции
  handlers[k]();
}

Выводит:

Номер функции: 5
Номер функции: 5
Номер функции: 5
Номер функции: 5
Номер функции: 5

А хочу получить:

Номер функции: 0
Номер функции: 1
Номер функции: 2
Номер функции: 3
Номер функции: 4

Такая же проблема с обработчиками событий:

var elements = document.querySelectorAll(".btn");
for (var counter = 0; counter < elements.length; counter++) {
  elements[counter].onclick = function() {
    console.log("Clicked button:", counter);
  };
}
<div class="btn">Кнопка А</div>
<div class="btn">Кнопка Б</div>
<div class="btn">Кнопка В</div>

И с асинхронным кодом тоже:

const delay = (time) => new Promise(resolve => setTimeout(resolve, time));

for (var counter = 0; counter < 4; counter++) {
  delay(counter * 200).then(() => console.log("Результат:", counter));
}

Как решить эту проблему с областью видимости?

Попробуй использовать метод bind: handlers[counter] = function(num) { console.log("Номер функции:", num); }.bind(null, counter); - это должно помочь. Или замените цикл на forEach, он создаёт новую область видимости. Почему используете var, а не let?

Можно еще через IIFE обернуть - старый проверенный способ. Вот так: handlers[counter] = (function(i) { return function() { console.log("Номер функции:", i); }; })(counter); Выглядит сложновато, но отлично работает даже в старых браузерах без поддержки let. Я всегда так делал до появления let - очень надёжно.

Классическая проблема замыканий! С var все функции ссылаются на одну переменную counter, которая в конце равна 5. Замените var на let в цикле - каждая итерация получит свою переменную:

for (let counter = 0; counter < 5; counter++) {
  handlers[counter] = function() {
    console.log("Номер функции:", counter);
  };
}

Это решит проблему.