Bit spät zu der Partei, aber ich war dieses Problem heute erkunden und bemerkte, dass viele der Antworten nicht vollständig Adresse, wie Javascript behandelt Bereiche, die im Wesentlichen, was dies läuft darauf hinaus, ist.
Wie viele andere bereits erwähnt haben, besteht das Problem darin, dass die innere Funktion auf die gleiche i
variabel. Warum erstellen wir also nicht einfach bei jeder Iteration eine neue lokale Variable und lassen die innere Funktion stattdessen auf diese verweisen?
//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]();
}
Genau wie zuvor, wo jede innere Funktion den letzten Wert ausgab, der der i
gibt nun jede innere Funktion nur noch den letzten Wert aus, der der ilocal
. Aber sollte nicht jede Iteration ihre eigene haben? ilocal
?
Wie sich herausstellt, ist das das Problem. Jede Iteration teilt den gleichen Bereich, so dass jede Iteration nach der ersten nur überschreiben ilocal
. Von MDN :
Wichtig: JavaScript hat keinen Blockbereich. Variablen, die mit einem Block eingeführt werden, sind auf die enthaltende Funktion oder das Skript beschränkt, und die Auswirkungen ihrer Einstellung bleiben über den Block selbst hinaus bestehen. Mit anderen Worten: Blockanweisungen führen keinen Geltungsbereich ein. Obwohl "Standalone"-Blöcke eine gültige Syntax sind, sollten Sie keine Standalone-Blöcke in JavaScript verwenden, da sie nicht das tun, was Sie denken, wenn Sie glauben, dass sie etwas Ähnliches wie solche Blöcke in C oder Java tun.
Zur Betonung wiederholt:
JavaScript hat keinen Blockbereich. Variablen, die mit einem Block eingeführt werden, sind auf die enthaltende Funktion oder das Skript beschränkt
Dies können wir sehen, indem wir prüfen ilocal
bevor wir sie in jeder Iteration deklarieren:
//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;
}
Das ist genau der Grund, warum dieser Fehler so heikel ist. Auch wenn Sie eine Variable neu deklarieren, wird Javascript keinen Fehler ausgeben, und JSLint wird nicht einmal eine Warnung ausgeben. Dies ist auch der Grund, warum der beste Weg, dies zu lösen ist, um die Vorteile von Schließungen, die im Wesentlichen die Idee, dass in Javascript, innere Funktionen haben Zugriff auf äußere Variablen, weil innere Bereiche "umschließen" äußeren Bereichen.
Das bedeutet auch, dass innere Funktionen äußere Variablen "festhalten" und sie am Leben erhalten, auch wenn die äußere Funktion zurückkehrt. Um dies zu nutzen, erstellen wir eine Wrapper-Funktion und rufen sie auf, um einen neuen Bereich zu erstellen, deklarieren ilocal
im neuen Bereich und geben eine innere Funktion zurück, die ilocal
(weitere Erklärung unten):
//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]();
}
Durch die Erstellung der inneren Funktion innerhalb einer Wrapper-Funktion erhält die innere Funktion eine private Umgebung, auf die nur sie zugreifen kann, eine "Closure". Jedes Mal, wenn wir die Wrapper-Funktion aufrufen, erstellen wir also eine neue innere Funktion mit einer eigenen, separaten Umgebung, die sicherstellt, dass die ilocal
Variablen nicht miteinander kollidieren und sich gegenseitig überschreiben. Ein paar kleinere Optimierungen ergeben die endgültige Antwort, die viele andere SO-Benutzer gaben:
//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);
};
}
Update
Da ES6 nun zum Mainstream geworden ist, können wir jetzt die neue let
Schlüsselwort, um Block-Scoped-Variablen zu erstellen:
//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]();
}
Sehen Sie, wie einfach es jetzt ist! Für mehr Informationen siehe diese Antwort auf die sich meine Informationen stützen.
62 Stimmen
Sind Sie sicher, dass Sie nicht wollen, dass
funcs
ein Array zu sein, wenn Sie numerische Indizes verwenden? Nur eine Vorwarnung.33 Stimmen
Das ist ein wirklich verwirrendes Problem. Diese Artikel hilft mir, ihn zu verstehen . Vielleicht hilft es ja auch anderen.
4 Stimmen
Eine weitere einfache und gut erklärte Lösung: 1) Verschachtelte Funktionen Zugriff auf den Bereich "über" ihnen haben ; 2) a Verschluss Lösung ... "Eine Schließung ist eine Funktion, die Zugriff auf den übergeordneten Bereich hat, auch nachdem die übergeordnete Funktion geschlossen wurde".
2 Stimmen
Siehe diesen Link zum besseren Verständnis javascript.info/tutorial/advanced-functions
61 Stimmen
Unter ES6 ist eine triviale Lösung die Deklaration der Variablen i mit lassen Sie die auf den Körper der Schleife beschränkt ist.
4 Stimmen
JS-Funktionen "verschließen" den Bereich, auf den sie bei der Deklaration Zugriff haben, und behalten den Zugriff auf diesen Bereich, auch wenn sich die Variablen in diesem Bereich ändern. Jede Funktion im obigen Array schließt sich über den globalen Bereich (global, einfach weil sie in diesem Bereich deklariert sind). Später werden diese Funktionen aufgerufen und protokollieren den aktuellsten Wert von
i
im globalen Bereich. Das ist JS : )let
anstelle vonvar
löst dieses Problem, indem es jedes Mal, wenn die Schleife ausgeführt wird, einen neuen Bereich erstellt, der für jede Funktion, die geschlossen werden soll, einen separaten Bereich erzeugt. Verschiedene andere Techniken tun das Gleiche mit zusätzlichen Funktionen.1 Stimmen
Eine ausführlichere Antwort finden Sie weiter unten: stackoverflow.com/a/41023816/1027004
0 Stimmen
Das Entwurfsmuster passt perfekt zu diesem Szenario stackoverflow.com/a/42512295/696535
0 Stimmen
Warum das nicht funktioniert - funcs[i] = function abc () { return function() { console.log("Mein Wert: " + i); };
0 Stimmen
Ein Beispiel für dieses Problem wurde hinzugefügt in
for in
yfor of
Schleifen.0 Stimmen
@S.D. Ich würde vorschlagen, zumindest die
var
Schlüsselwort für das hinzugefügte Beispiel. Die Verwendung globaler Variablen gilt als schlechte Praxis.for (var v of arr)
(Um das Problem zu lösen, verwenden Sielet
oconst
anstelle vonvar
.)0 Stimmen
@3limin4t0r Korrigiert.
3 Stimmen
Aus diesem Grund hasse ich Javascript.