Немного опоздал на вечеринку, но я изучал эту проблему сегодня и заметил, что многие ответы не полностью касаются того, как 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]();
}
Посмотри, как легко сейчас! Для получения дополнительной информации см. этот ответ , на котором основана моя информация.