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
Ich möchte hingegen, dass es ausgegeben wird:
Mein Wert: 0 Mein Wert: 1 Mein Wert: 2
Das gleiche Problem tritt auf, wenn die Verzögerung bei der Ausführung 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, z.B. 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));
}
Was ist die Lösung für dieses grundlegende Problem?
Nun, das Problem ist, dass die Variable "i" in jeder Ihrer anonymen Funktionen an dieselbe Variable außerhalb der Funktion gebunden ist.
Sie wollen die Variable innerhalb jeder Funktion an einen separaten, unveränderlichen Wert außerhalb der Funktion binden:
var funcs = [];
function createfunc(i) {
return function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Da es in JavaScript keinen Blockbereich gibt, sondern nur einen Funktionsbereich, stellen Sie durch das Einschließen der Funktionserstellung in eine neue Funktion sicher, dass der Wert von "i" so bleibt, wie Sie es beabsichtigt haben.
Mit der relativ weit verbreiteten Verfügbarkeit der Funktion Array.prototype.forEach
(im Jahr 2015) ist es erwähnenswert, dass in Situationen, in denen es um Iteration hauptsächlich über ein Array von Werten geht, .forEach()
einen sauberen, natürlichen Weg bietet, um einen eindeutigen Abschluss für jede Iteration zu erhalten. Das heißt, angenommen, Sie haben eine Art Array mit Werten (DOM-Referenzen, Objekte, was auch immer), und das Problem entsteht, Rückrufe spezifisch für jedes Element einzurichten, können Sie dies tun:
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
Die Idee ist, dass jeder Aufruf der Callback-Funktion, die mit der .forEach
-Schleife verwendet wird, eine eigene Closure ist. Der Parameter, der an diesen Handler übergeben wird, ist das Array-Element, das für diesen bestimmten Schritt der Iteration spezifisch ist. Wenn es in einem asynchronen Rückruf verwendet wird, kollidiert es nicht mit einem der anderen Rückrufe, die in anderen Schritten der Iteration eingerichtet wurden.
Wenn Sie mit jQuery arbeiten, bietet Ihnen die Funktion "$.each()" eine ähnliche Möglichkeit.
let
ECMAScript 6 (ES6) führt neue let
- und const
-Schlüsselwörter ein, die anders skaliert sind als var
-basierte Variablen. In einer Schleife mit einem let
-basierten Index wird beispielsweise bei jeder Iteration durch die Schleife ein neuer Wert von i
angezeigt, wobei jeder Wert innerhalb der Schleife zugewiesen wird, so dass Ihr Code wie erwartet funktioniert. Es gibt viele Quellen, aber ich empfehle [2ality's block-scoping post] (http://www.2ality.com/2015/02/es6-scoping.html) als eine großartige Informationsquelle.
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 zwar let
unterstützen, aber das oben beschriebene falsch machen (sie erstellen nicht jedes Mal ein neues i
, so dass alle oben genannten Funktionen 3 protokollieren würden, wie sie es tun würden, wenn wir var
verwenden würden). Edge 14 macht es endlich richtig.
Versuchen Sie es:
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]();
}
Edit (2014):
Persönlich denke ich, dass @Austs [neuere Antwort über die Verwendung von .bind
] (https://stackoverflow.com/a/19323214/918959) jetzt der beste Weg ist, um diese Art von Dingen zu tun. Es gibt auch lo-dash/underscore's _.partial
, wenn man sich nicht mit bind
's thisArg
herumschlagen muss oder will.
Der Grund dafür, dass Ihr ursprüngliches Beispiel nicht funktioniert hat, ist, dass alle von Ihnen in der Schleife erstellten Abschlüsse auf denselben Rahmen verweisen. Das heißt, Sie hatten 3 Methoden für ein Objekt mit nur einer einzigen Variablen "i". Sie gaben alle denselben Wert aus.