Немного опоздал на вечеринку, но я изучал этот вопрос сегодня и заметил, что многие ответы не полностью касаются того, как 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переменные не конфликтуют и не перезаписывают друг друга. Несколько небольших оптимизаций дают окончательный ответ, который дали многие другие пользователи SO:
//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быть массивом, если используете числовые индексы? Просто один на один.