var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Es gibt dies aus:
Mein Wert: 3
Mein Wert: 3
Mein Wert: 3
Während ich möchte, dass es ausgibt:
Mein Wert: 0
Mein Wert: 1
Mein Wert: 2
Das gleiche Problem tritt auf, wenn die Verzögerung beim Ausführen der Funktion durch die Verwendung von Ereignis-Listenern verursacht wird:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
… oder asynchroner Code, zB mit Promises:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
Es zeigt sich auch in for in
und for of
Schleifen:
const arr = [1,2,3];
const fns = [];
for(var i in arr){
fns.push(() => console.log(`index: ${i}`));
}
for(var v of arr){
fns.push(() => console.log(`value: ${v}`));
}
for(var f of fns){
f();
}
Was ist die Lösung für dieses grundlegende Problem?
Versuchen:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Bearbeiten (2014):
Persönlich denke ich, @Austs neuere Antwort zur Verwendung .bind
ist der beste Weg, so etwas jetzt zu tun. Es gibt auch Lo-Bindestriche/Unterstriche _.partial
wenn Sie nicht brauchen oder wollen bind
‘S thisArg
.
Mit einem Sofort aufgerufener Funktionsausdruckdie einfachste und lesbarste Art, eine Indexvariable einzuschließen:
for (var i = 0; i < 3; i++) {
(function(index) {
console.log('iterator: ' + index);
//now you can also loop an ajax call here
//without losing track of the iterator value: $.ajax({});
})(i);
}
Dies sendet den Iterator i
in die anonyme Funktion, die wir definieren als index
. Dadurch entsteht ein Abschluss, bei dem die Variable i
wird für die spätere Verwendung in jeder asynchronen Funktionalität innerhalb des IIFE gespeichert.
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 hinausläuft.
Wie viele andere bereits erwähnt haben, besteht das Problem darin, dass die innere Funktion auf dasselbe verweist i
Variable. Warum erstellen wir also nicht einfach bei jeder Iteration eine neue lokale Variable und lassen stattdessen die innere Funktion darauf 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 zuletzt zugewiesenen Wert ausgab i
jetzt gibt jede innere Funktion nur den letzten zugewiesenen Wert aus ilocal
. Aber sollte nicht jede Iteration ihre eigene haben ilocal
?
Es stellt sich heraus, das ist das Problem. Jede Iteration teilt sich denselben Bereich, sodass jede Iteration nach der ersten nur überschrieben wird ilocal
. Von MDN:
Wichtig: JavaScript hat keinen Blockbereich. Variablen, die mit einem Block eingeführt werden, sind auf die enthaltende Funktion oder das enthaltende 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 “eigenständige” Blöcke eine gültige Syntax sind, sollten Sie keine eigenständigen Blöcke in JavaScript verwenden, da sie nicht das tun, was Sie denken, wenn Sie denken, dass sie so etwas wie solche Blöcke in C oder Java tun.
Wiederholt zur Betonung:
JavaScript hat keinen Blockbereich. Variablen, die mit einem Block eingeführt werden, sind auf die enthaltende Funktion oder das Skript beschränkt
Wir können dies sehen, indem wir überprüfen ilocal
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 aus diesem Grund ist dieser Fehler so knifflig. Auch wenn 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, Closures zu nutzen, was im Wesentlichen die Idee ist, dass in Javascript innere Funktionen Zugriff auf äußere Variablen haben, weil 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 Geltungsbereich zu deklarieren ilocal
im neuen Geltungsbereich und geben eine innere Funktion zurück, die verwendet ilocal
(mehr 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]();
}
Das Erstellen der inneren Funktion innerhalb einer Wrapper-Funktion gibt der inneren 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 kollidieren nicht und überschreiben sich gegenseitig. 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
Da ES6 jetzt Mainstream ist, können wir jetzt das Neue nutzen let
Schlüsselwort zum Erstellen von blockbezogenen Variablen:
//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.
Da ES6 jetzt weithin unterstützt wird, hat sich die beste Antwort auf diese Frage geändert. ES6 bietet die let
und const
Schlüsselwörter für genau diesen Umstand. Anstatt mit Verschlüssen herumzuspielen, können wir einfach verwenden let
um eine Loop-Scope-Variable wie folgt zu setzen:
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
val
zeigt dann auf ein Objekt, das spezifisch für diese bestimmte Wendung der Schleife ist, und gibt den korrekten Wert ohne die zusätzliche Abschlussnotation zurück. Dies vereinfacht dieses Problem offensichtlich erheblich.
const
ist ähnlich wie let
mit der zusätzlichen Einschränkung, dass der Variablenname nach der anfänglichen Zuweisung nicht an eine neue Referenz gebunden werden kann.
Die Browserunterstützung ist jetzt für diejenigen da, die auf die neuesten Browserversionen abzielen. const
/let
werden derzeit in den neuesten Versionen von Firefox, Safari, Edge und Chrome unterstützt. Es wird auch in Node unterstützt, und Sie können es überall verwenden, indem Sie Build-Tools wie Babel nutzen. Ein funktionierendes Beispiel sehen Sie hier: http://jsfiddle.net/ben336/rbU4t/2/
Dokumente hier:
Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 unterstützt werden let
aber verstehe das obige falsch (sie erstellen keine neue i
jedes Mal, also würden alle oben genannten Funktionen 3 protokollieren, wie sie es tun würden, wenn wir sie verwenden würden var
). Edge 14 macht es endlich richtig.
Du willst sicher nicht
funcs
ein Array sein, wenn Sie numerische Indizes verwenden? Nur ein Kopf hoch.– DanMan
26. Juli 2013 um 11:12 Uhr
Das ist ein wirklich verwirrendes Problem. Dies Artikel hilft mir, es zu verstehen. Vielleicht hilft es auch anderen.
– Benutzer3199690
3. Mai 2014 um 15:38 Uhr
Eine weitere einfache und erklärte Lösung: 1) Verschachtelte Funktionen haben Zugriff auf den Geltungsbereich “über” ihnen; 2) ein Schließung Lösung… “Ein Abschluss ist eine Funktion, die Zugriff auf den übergeordneten Bereich hat, auch nachdem die übergeordnete Funktion geschlossen wurde”.
– Peter Krauß
17. Dezember 2014 um 1:22 Uhr
Verweisen Sie auf diesen Link für ein besseres Verständnis javascript.info/tutorial/advanced-functions
– Saurabh Ahuja
2. April 2015 um 12:01 Uhr
Im ES6ist eine triviale Lösung, die Variable zu deklarieren ich mit Lassendie auf den Schleifenkörper beschränkt ist.
– Thomas Nikodym
13. September 2016 um 20:34 Uhr