Если создать функцию внутри цикла, линтер мудро скажет: “Don’t make functions within a loop”, но почему этого не рекомендуется делать? Давайте разберемся. Функции в JavaScript являются объектами, и создавая их в цикле из функционального выражения, интерпретатор создает множество независимых одинаковых экземляров объектов с выделением под них памяти, что может навредить не только производительности, но и будущему сопровождению кода. Но если не следовать рекомендации, что в этом плохого?

Что ж, попробуем в лоб наполнить массив абстрактных коллбэков функциями, которые завязаны на итератор для выполнения какой-то абстрактной работы

  var someCallbacks = [];

  function doStuff(index) {
      console.log(`Doing stuff with index ${index}`);
  }

  for (var i = 0; i < 5; i++) {
      someCallbacks.push(function() {
          doStuff(i);
      });
  }

  for (var j = 0; j < someCallbacks.length; j++) {
      someCallbacks[j]();
  }


  // Doing stuff with index 5
  // Doing stuff with index 5
  // Doing stuff with index 5
  // Doing stuff with index 5
  // Doing stuff with index 5

Немного неожиданно? Область видимости i не ограничивается блоком цикла (потому что i объявлена с ключевым словом var, она как бы выше цикла, в начале текущей функции - это называется подъемом). Далее коллбэки проявляют свойства замыканий, ссылаясь к этой переменной i по цепочке областей видимости, а её итоговое значение после финального выражения первого цикла равно пяти. Есть несколько вариантов решения проблемы.

es6 let

  var someCallbacks = [];

  function doStuff(index) {
      console.log(`Doing stuff with index ${index}`);
  }

  for (let i = 0; i < 5; i++) { // *
      someCallbacks.push(function() {
          doStuff(i);
      });
  }

  for (var j = 0; j < someCallbacks.length; j++) {
      someCallbacks[j]();
  }


  // Doing stuff with index 0
  // Doing stuff with index 1
  // Doing stuff with index 2
  // Doing stuff with index 3
  // Doing stuff with index 4

Здесь (*) каждая итерация создает новую лексическую область видимости, связанную с предыдущей, формируя цепь. Таким образом в каждой итерации цикла существует независимая переменная i, и замыкания ссылаются на разные экземпляры i из конкретной итерации.

Кстати, циклы с var в среднем на 1-2% быстрее циклов с let

Если обстоятельства вынуждают писать на старом стандарте, есть еще способ с замыканием-оберткой.

Замыкание-обертка

  var someCallbacks = [];

  function doStuff(index) {
      console.log(`Doing stuff with index ${index}`);
  }

  for (var i = 0; i < 5; i++) {
      (function(index) {
          someCallbacks.push(function() {
              doStuff(index);
          });
      })(i);
  }

  for (var j = 0; j < someCallbacks.length; j++) {
      someCallbacks[j]();
  }


  // Doing stuff with index 0
  // Doing stuff with index 1
  // Doing stuff with index 2
  // Doing stuff with index 3
  // Doing stuff with index 4

И всё же, прежде чем создавать функции в цикле, нужно убедиться в том, что это того стоит и создание функции нельзя разместить вне цикла, вот так:

  var someCallbacks = [];

  function doStuff(index) {
    console.log(`Doing stuff with index ${index}`);
  }

  function makeCallback(index) {
    return function() {
      doStuff(index);
    };
  }

  for (var i = 0; i < 5; i++) {
    someCallbacks.push(makeCallback(i));
  }

  for (var j = 0; j < someCallbacks.length; j++) {
    someCallbacks[j]();
  }

  // Doing stuff with index 0
  // Doing stuff with index 1
  // Doing stuff with index 2
  // Doing stuff with index 3
  // Doing stuff with index 4