Trochę za późno na imprezę, ale dzisiaj zgłębiałem ten problem i zauważyłem, że wiele odpowiedzi nie do końca odnosi się do tego, jak JavaScript traktuje zakresy, co w gruncie rzeczy sprowadza się do tego.
Tak jak wielu innych wspomniano, problemem jest to, że funkcja wewnętrzna odnosi się do tej samej izmiennej. Dlaczego więc po prostu nie tworzymy nowej zmiennej lokalnej przy każdej iteracji i zamiast tego mamy odwołanie do funkcji wewnętrznej?
//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]();
}
Tak jak poprzednio, gdzie każda funkcja wewnętrzna generowała ostatnią przypisaną wartość i, teraz każda funkcja wewnętrzna tylko wyświetla ostatnią przypisaną wartość ilocal. Ale nie każda iteracja powinna mieć swoją własnąilocal ?
Okazuje się, że to jest problem. Każda iteracja ma ten sam zakres, więc każda iteracja po pierwszej jest po prostu nadpisywana ilocal. Z MDN :
Ważne: JavaScript nie ma zasięgu blokowego. Zmienne wprowadzone wraz z blokiem mają zakres obejmujący funkcję lub skrypt, a efekty ich ustawienia utrzymują się poza samym blokiem. Innymi słowy, instrukcje blokowe nie wprowadzają zakresu. Chociaż „samodzielne” bloki są poprawną składnią, nie chcesz używać samodzielnych bloków w JavaScript, ponieważ nie robią tego, co myślisz, że robią, jeśli uważasz, że robią coś takiego jak takie bloki w C lub Java.
Podkreślono dla podkreślenia:
JavaScript nie ma zasięgu blokowego. Zmienne wprowadzane za pomocą bloku mają zakres obejmujący funkcję lub skrypt
Możemy to sprawdzić, sprawdzając, ilocalzanim zadeklarujemy to w każdej iteracji:
//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;
}
Właśnie dlatego ten błąd jest tak trudny. Nawet jeśli ponownie zmieniasz zmienną, JavaScript nie wyśle błędu, a JSLint nawet nie wyśle ostrzeżenia. Dlatego też najlepszym sposobem rozwiązania tego jest skorzystanie z zamknięć, co jest w istocie ideą, że w Javascripcie funkcje wewnętrzne mają dostęp do zmiennych zewnętrznych, ponieważ wewnętrzne zakresy „otaczają” zewnętrzne zakresy.

Oznacza to również, że funkcje wewnętrzne „trzymają” zewnętrzne zmienne i utrzymują je przy życiu, nawet jeśli funkcja zewnętrzna powróci. Aby to wykorzystać, tworzymy i wywołujemy funkcję otoki wyłącznie w celu utworzenia nowego zakresu, zadeklarowania ilocalw nowym zakresie i zwrócenia używanej funkcji wewnętrznej ilocal(więcej wyjaśnień poniżej):
//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]();
}
Utworzenie funkcji wewnętrznej w funkcji opakowania daje funkcji wewnętrznej prywatne środowisko, do którego tylko on może uzyskać dostęp, „zamknięcie”. Dlatego za każdym razem, gdy wywołujemy funkcję otoki, tworzymy nową funkcję wewnętrzną z własnym oddzielnym środowiskiem, zapewniając, że ilocalzmienne się nie kolidują i nie nadpisują. Kilka drobnych optymalizacji daje ostateczną odpowiedź, którą podało wielu innych użytkowników 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);
};
}
Aktualizacja
Dzięki ES6 mainstream możemy teraz używać nowego letsłowa kluczowego do tworzenia zmiennych o zasięgu blokowym:
//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]();
}
Zobacz, jakie to jest teraz łatwe! Aby uzyskać więcej informacji, zobacz tę odpowiedź , na której oparte są moje informacje.
funcsbyć tablicą, jeśli używasz indeksów numerycznych? Tylko jedna głowa do góry.