Немного поздно для вечеринки, но я изучал эту проблему сегодня и заметил, что многие ответы не полностью касаются того, как 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
был массивом, если вы используете числовые индексы? Просто предупреждаю.Это действительно сбивающая с толку проблема. Эта статья помогла мне понять это. Может, это поможет и другим.
Другое простое и понятное решение: 1) Вложенные функции имеют доступ к области «над» ними; 2) a закрытие решение ... «Закрытие - это функция, имеющая доступ к родительской области, даже после закрытия родительской функции».
Обратитесь к этой ссылке для лучшего понимания javascript.info/tutorial/advanced-functions
В ES6 тривиальным решением является объявление переменной i с помощью let, которая ограничена до тела цикла.
Функции JS «закрывают» область видимости, к которой они имеют доступ после объявления, и сохраняют доступ к этой области даже при изменении переменных в этой области. Каждая функция в приведенном выше массиве закрывается в глобальной области (глобальной просто потому, что это область, в которой они объявлены). Позже эти функции вызываются, регистрируя самое последнее значение
i
в глобальной области. Это JS :)let
вместоvar
решает эту проблему, создавая новую область видимости при каждом запуске цикла, создавая отдельную область видимости для каждой функции, которую нужно закрыть. Различные другие методы делают то же самое с дополнительными функциями.Ниже дан более подробный ответ: coderhelper.com/a/41023816/1027004
Шаблон проектирования идеально соответствует этому сценарию coderhelper.com/a/42512295/696535
почему это не работает - funcs [i] = function abc () {return function () {console.log ("My value:" + i); };
Добавлен пример этой проблемы в циклы
for in
иfor of
.@ S.D. Я бы посоветовал хотя бы использовать ключевое слово
var
для добавленного примера. Использование глобальных переменных считается плохой практикой.for (var v of arr)
(Для решения проблемы используйтеlet
илиconst
вместоvar
.)@ 3limin4t0r Поправил.
Вот почему я ненавижу JavaScript.