Армія функцій
Наступний код створює масив shooters.
Кожна функція має вивести свій номер. Але щось не так…
function makeArmy() {
  let shooters = [];
  let i = 0;
  while (i < 10) {
    let shooter = function() { // створюємо функцію стрільця,
      alert( i ); // що має показувати свій номер
    };
    shooters.push(shooter); // додаємо її до масиву
    i++;
  }
  // ...і повертаємо масив стрільців
  return shooters;
}
let army = makeArmy();
// всі стрільці показують 10 замість своїх номерів 0, 1, 2, 3...
army[0](); // 10 від стрільця за номером 0
army[1](); // 10 від стрільця за номером 1
army[2](); // 10 ...і так далі.Чому всі функції показують однакове значення?
Виправте код так, щоб він працював як передбачалося.
Давайте розберемося, що саме відбувається всередині функції makeArmy, і рішення стане очевидним.
- 
Функція створює порожній масив shooters:let shooters = [];
- 
Наповнює його функціями у циклі через shooters.push(function).Кожен елемент є функцією, тому отриманий масив виглядає так: shooters = [ function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); } ];
- 
Функція повертає масив. Потім, виклик будь-якого елемента масиву, наприклад army[5]()отримає елементarmy[5]з масиву (який є функцією) і викликає її.Чому всі функції показують однакове значення, 10?Зверніть увагу, що всередині функцій shooterнемає локальної змінноїi. Коли така функція викликається, вона приймаєiзі свого зовнішнього лексичного середовища.Тоді яке буде значення i?Якщо ми подивимося на код: function makeArmy() { ... let i = 0; while (i < 10) { let shooter = function() { // функція shooter alert( i ); // має показати свій номер }; shooters.push(shooter); // додати функцію до масиву i++; } ... }Ми бачимо що усі функції shooterстворені в лексичному середовищі функціїmakeArmy(). Але коли ми викликаємоarmy[5](), функціяmakeArmyвже закінчила свою роботу, і остаточне значенняiце10(циклwhileзупиняється наi=10).В результаті всі функції shooterотримують однакове значення із зовнішнього лексичного середовища, тобто останнє значення,i=10.Як ви можете бачити вище, на кожній ітерації циклу while {...}, створюється нове лексичне середовище. Отже, щоб виправити це, ми можемо скопіювати значенняiу змінну всередині блокуwhile {...}, ось так:function makeArmy() { let shooters = []; let i = 0; while (i < 10) { let j = i; let shooter = function() { // функція shooter alert( j ); // має показати свій номер }; shooters.push(shooter); i++; } return shooters; } let army = makeArmy(); // Тепер код працює правильно army[0](); // 0 army[5](); // 5Тут let j = iоголошує локальну зміннуjта копіює до неї номер ітерації зі змінноїi. Примітиви копіюються “за значенням”, тому ми фактично отримуємо незалежну копіюi, що належить до поточної ітерації циклу.Функції тепер працюють правильно, тому що змінна i“живе” трохи ближче. Не в лексичному середовищі викликуmakeArmy(), але в лексичному середовищі, яке відповідає поточній ітерації циклу:Такої проблеми також можна було б уникнути, якби ми використали цикл forз самого початку, ось так:function makeArmy() { let shooters = []; for(let i = 0; i < 10; i++) { let shooter = function() { // функція shooter alert( i ); // має показати свій номер }; shooters.push(shooter); } return shooters; } let army = makeArmy(); army[0](); // 0 army[5](); // 5Це, по суті, те саме, тому що forна кожній ітерації створює нове лексичне середовище зі своєю змінноюi. Томуshooterзгенерований на кожній ітерації бере посилання на зміннуi, з тієї самої ітерації.
Тепер, коли ви доклали так багато зусиль, щоб прочитати це, остаточний рецепт такий простий – використовуйте цикл for, ви можете задатися питанням – чи було воно того варте?
Ну, якби ви могли легко відповісти на запитання, ви б не читали рішення. Тож, сподіваюся, це завдання допомогло вам трохи краще зрозуміти як все працює.
Крім того, на практиці бувають випадки, коли віддають перевагу while замість for, та інші сценарії, де такі проблеми є реальними.