Etwas spät zur Party, aber ich habe dieses Problem heute untersucht und festgestellt, dass viele der Antworten nicht vollständig darauf eingehen, wie Javascript Bereiche behandelt, worauf es im Wesentlichen ankommt.
Wie viele andere erwähnt haben, besteht das Problem darin, dass die innere Funktion auf dieselbe i
Variable verweist . Warum erstellen wir nicht einfach bei jeder Iteration eine neue lokale Variable und haben stattdessen die innere Funktionsreferenz darauf?
//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 zuletzt zugewiesenen Wert ausgegeben hat i
, gibt jetzt jede innere Funktion nur den zuletzt zugewiesenen Wert aus ilocal
. Aber sollte nicht jede Iteration ihre eigene haben ilocal
?
Es stellt sich heraus, das ist das Problem. Jede Iteration hat denselben Gültigkeitsbereich, sodass jede Iteration nach der ersten nur überschrieben wird ilocal
. Von MDN :
Wichtig: JavaScript hat keinen Blockbereich. Mit einem Block eingeführte Variablen sind auf die enthaltene 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 Bereich ein. Obwohl "eigenständige" Blöcke eine gültige Syntax sind, möchten Sie keine eigenständigen Blöcke in JavaScript verwenden, da sie nicht das tun, was Sie denken, wenn Sie glauben, dass sie solche Blöcke in C oder Java ausführen.
Zur Betonung wiederholt:
JavaScript hat keinen Blockbereich. Mit einem Block eingeführte Variablen sind auf die enthaltende Funktion oder das Skript beschränkt
Wir können dies sehen, indem ilocal
wir überprüfen, bevor wir es 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;
}
Genau deshalb ist dieser Fehler so schwierig. Obwohl Sie eine Variable neu deklarieren, gibt Javascript keinen Fehler aus und JSLint gibt nicht einmal eine Warnung aus. Dies ist auch der Grund, warum der beste Weg, dies zu lösen, darin besteht, Verschlüsse zu nutzen. Dies ist im Wesentlichen die Idee, dass innere Funktionen in Javascript Zugriff auf äußere Variablen haben, da innere Bereiche äußere Bereiche "einschließen".
Dies bedeutet auch, dass innere Funktionen äußere Variablen "festhalten" und am Leben erhalten, selbst wenn die äußere Funktion zurückkehrt. Um dies zu nutzen, erstellen und rufen wir eine Wrapper-Funktion auf, um lediglich einen neuen Bereich zu erstellen, den neuen Bereich zu deklarieren ilocal
und eine innere Funktion zurückzugeben, die verwendet ilocal
(weitere Erläuterungen 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 das Erstellen der inneren Funktion innerhalb einer Wrapper-Funktion erhält die innere Funktion eine private Umgebung, auf die nur sie zugreifen kann, einen "Abschluss". Daher erstellen wir jedes Mal, wenn wir die Wrapper-Funktion aufrufen, eine neue innere Funktion mit einer eigenen separaten Umgebung, um sicherzustellen, dass die ilocal
Variablen nicht kollidieren und sich gegenseitig überschreiben. Ein paar kleinere Optimierungen geben die endgültige Antwort, die viele andere SO-Benutzer gegeben haben:
//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);
};
}
Aktualisieren
Mit ES6 als Mainstream können wir jetzt das neue let
Schlüsselwort verwenden, um Variablen mit Blockbereich 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]();
}
Schau wie einfach es jetzt ist! Weitere Informationen finden Sie in dieser Antwort , auf der meine Informationen basieren.
funcs
sicher kein Array sein, wenn Sie numerische Indizes verwenden? Nur ein Kopf hoch.