Трохи запізнюючись на вечірку, але я сьогодні вивчав це питання і зауважив, що багато відповідей не повністю стосуються того, як Javascript поводиться зі сферами дії, що, по суті, і зводиться до цього.
Отже, як багато інших згаданих, проблема полягає в тому, що внутрішня функція посилається на ту саму i
змінну. То чому б ми не просто створили нову локальну змінну кожної ітерації та не мали натомість внутрішнього посилання на функцію?
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
var ilocal = i; //create a new local variable
funcs[i] = function() {
console.log("My value: " + ilocal); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Як і раніше, коли кожна внутрішня функція виводила останнє значення, призначене i
, тепер кожна внутрішня функція просто виводить останнє значення, призначене ilocal
. Але чи не повинна кожна ітерація мати свою власну ilocal
?
Виявляється, ось у чому проблема. Кожна ітерація має однаковий обсяг, тому кожна ітерація після першої - просто перезапис ilocal
. Від MDN :
Важливо: JavaScript не має області блоку. Змінні, введені разом з блоком, відносяться до функції, що містить вміст, або сценарій, і ефекти їх встановлення зберігаються поза самим блоком. Іншими словами, блок-оператори не вводять сферу застосування. Хоча "автономні" блоки є синтаксисом дійсного, ви не хочете використовувати окремі блоки в JavaScript, тому що вони не роблять те, що ви думаєте, що роблять, якщо ви думаєте, що вони роблять щось подібне до таких блоків на C або Java.
Ще раз підтвердили:
У JavaScript немає області блоку. Змінні, введені разом з блоком, відносяться до функції, що містить або сценарій
Ми можемо побачити це, перевіривши, ilocal
перш ніж ми оголосимо про це в кожній ітерації:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
console.log(ilocal);
var ilocal = i;
}
Саме тому ця помилка настільки хитра. Незважаючи на те, що ви переосмислюєте змінну, Javascript не видасть помилку, а JSLint навіть не видасть попередження. Ось чому найкращий спосіб вирішити це - скористатись закриттям, що, по суті, полягає в тому, що в Javascript внутрішні функції мають доступ до зовнішніх змінних, оскільки внутрішні області "закривають" зовнішні області.
Це також означає, що внутрішні функції "тримаються" за зовнішні змінні та підтримують їх живими, навіть якщо зовнішня функція повертається. Щоб скористатися цим, ми створимо і викличемо функцію обгортки виключно для того, щоб створити нову область, оголосити ilocal
в новій області і повернути внутрішню функцію, яка використовує ilocal
(докладніше пояснення):
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = (function() { //create a new scope using a wrapper function
var ilocal = i; //capture i into a local var
return function() { //return the inner function
console.log("My value: " + ilocal);
};
})(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Створення внутрішньої функції всередині функції обгортки дає внутрішній функції приватне середовище, до якого лише вона може отримати доступ, "закриття". Таким чином, кожен раз, коли ми викликаємо функцію обгортки, ми створюємо нову внутрішню функцію із власним окремим середовищем, забезпечуючи, щоб ilocal
змінні не стикалися та перезаписували один одного. Кілька незначних оптимізацій дає остаточну відповідь, яку дали багато інших користувачів ЗП:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
return function() { //return the inner function
console.log("My value: " + ilocal);
};
}
Оновлення
Завдяки ES6, що тепер є основним, тепер ми можемо використовувати нове let
ключове слово для створення змінних блоку:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
funcs[i] = function() {
console.log("My value: " + i); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
funcs[j]();
}
Подивіться, як легко зараз! Для отримання додаткової інформації дивіться цю відповідь , на якій базується моя інформація.
funcs
бути масивом, якщо використовуєте числові індекси? Просто голови вгору.