stackoverflow - JavaScript-Verschluss in Schleifen-einfaches praktisches Beispiel
stackoverflow javascript (20)
Wir werden prüfen, was tatsächlich passiert , wenn Sie erklären ,
var
undlet
eins nach dem anderen.
Fall1 : verwendenvar
<script>
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
</script>
Öffnen Sie nun Ihr Chrome-Konsolenfenster, indem Sie die Taste F12 drücken, und aktualisieren Sie die Seite. Verbrauchen Sie alle drei Funktionen innerhalb des Arrays. Sie sehen eine Eigenschaft mit dem Namen [[Scopes]]
.Expand diese. Sie sehen ein Array-Objekt mit dem Namen " "Global"
Erweitern". Sie finden eine 'i'
in dem Objekt deklarierte Eigenschaft mit dem Wert 3.
Fazit:
- Wenn Sie eine Variable mit
'var'
einer Funktion außerhalb einer Funktion deklarieren , wird sie zu einer globalen Variablen (Sie können dies durch Eingabe voni
oderwindow.i
im Konsolenfenster überprüfen. - Die anonyme Funktion, die Sie deklariert haben, ruft den Wert in der Funktion nur auf, wenn Sie die Funktionen aufrufen.
- Wenn Sie die Funktion aufrufen,
console.log("My value: " + i)
übernimmt sie den Wert aus demGlobal
Objekt und zeigt das Ergebnis an.
CASE2: Mit let
Ersetzen Sie jetzt 'var'
mit'let'
<script>
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
</script>
Machen Sie dasselbe, gehen Sie zu den Bereichen. Nun sehen Sie zwei Objekte "Block"
und "Global"
. Erweitern Block
Sie nun das Objekt, Sie werden sehen, dass 'i' dort definiert ist, und das Merkwürdige ist, dass für jede Funktion der Wert if i
unterschiedlich ist (0, 1, 2).
Fazit:
Wenn Sie Variable deklarieren 'let'
auch außerhalb der Funktion , sondern innerhalb der Schleife, wird diese Variable nicht eine globale Variable sein, wird es eine gewordene Block
Ebene Variable , die nur für die gleiche Funktion zur Verfügung steht only.That der Grund ist, werden wir Wert immer i
unterschiedlich für jede Funktion, wenn wir die Funktionen aufrufen.
Für weitere Informationen über die nähere Vorgehensweise lesen Sie das fantastische Video-Tutorial https://youtu.be/71AtaJpJHw0
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Es gibt dies aus:
Mein Wert: 3
Mein Wert: 3
Mein Wert: 3
Während ich möchte, dass es ausgegeben wird:
Mein Wert: 0
Mein Wert: 1
Mein Wert: 2
Dasselbe 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");
for (var i = 0; i < buttons.length; i++) { // let's create 3 functions
buttons[i].addEventListener("click", function() { // as event listeners
console.log("My value: " + i); // each should log its value.
});
}
<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++){
wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}
Was ist die Lösung für dieses grundlegende Problem?
Versuchen Sie es mit dem kürzeren
kein Array
kein extra für die Schleife
for (var i = 0; i < 3; i++) {
createfunc(i)();
}
function createfunc(i) {
return function(){console.log("My value: " + i);};
}
Das Hauptproblem bei dem vom OP angezeigten Code ist, dass i
niemals bis zur zweiten Schleife gelesen wird. Stellen Sie sich vor, Sie sehen einen Fehler im Code
funcs[i] = function() { // and store them in funcs
throw new Error("test");
console.log("My value: " + i); // each should log its value.
};
Der Fehler tritt tatsächlich erst auf, wenn funcs[someIndex]
ausgeführt wird ()
. Bei Verwendung dieser Logik sollte offensichtlich sein, dass der Wert von i
auch bis zu diesem Punkt nicht erfasst wird. Sobald die ursprüngliche Schleife beendet ist, bringt i++
i
auf den Wert 3
was dazu führt, dass die Bedingung i < 3
ausfällt und die Schleife endet. Zu diesem Zeitpunkt ist i
3
und wenn funcs[someIndex]()
verwendet wird und i
ausgewertet wird, ist es 3 - jedes Mal.
Um dies zu überwinden, müssen Sie i
wie es angetroffen wird, bewerten. Beachten Sie, dass dies bereits in Form von funcs[i]
(mit 3 eindeutigen Indizes) geschehen ist. Es gibt mehrere Möglichkeiten, diesen Wert zu erfassen. Man muss es als Parameter an eine Funktion übergeben, die hier bereits mehrfach dargestellt ist.
Eine andere Möglichkeit besteht darin, ein Funktionsobjekt zu erstellen, das die Variable schließen kann. Das kann so erreicht werden
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
Die Verwendung eines sofort aufgerufenen Funktionsausdrucks ist die einfachste und lesbarste Methode zum Umschließen einer Indexvariablen:
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);
}
Dadurch wird der Iterator i
an die anonyme Funktion gesendet, deren index
wir definieren. Dadurch wird eine Schließung erstellt, bei der die Variable i
für die spätere Verwendung in einer asynchronen Funktionalität innerhalb des IIFE gespeichert wird.
Dies beschreibt den häufigsten Fehler bei der Verwendung von Verschlüssen in JavaScript.
Eine Funktion definiert eine neue Umgebung
Erwägen:
function makeCounter()
{
var obj = {counter: 0};
return {
inc: function(){obj.counter ++;},
get: function(){return obj.counter;}
};
}
counter1 = makeCounter();
counter2 = makeCounter();
counter1.inc();
alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0
Bei jedem makeCounter
{counter: 0}
dass ein neues Objekt erstellt wird. Außerdem wird eine neue Kopie von obj
erstellt, um auf das neue Objekt zu verweisen. Somit sind counter1
und counter2
unabhängig voneinander.
Verschlüsse in Schleifen
Die Verwendung eines Abschlusses in einer Schleife ist schwierig.
Erwägen:
var counters = [];
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
}
makeCounters(2);
counters[0].inc();
alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1
Beachten Sie, dass counters[0]
und counters[1]
nicht unabhängig sind. In der Tat arbeiten sie auf das gleiche obj
!
Dies liegt daran, dass es nur eine Kopie von obj
alle Iterationen der Schleife gibt, möglicherweise aus Performancegründen. Obwohl {counter: 0}
in jeder Iteration ein neues Objekt erstellt, wird dieselbe Kopie von obj
nur mit einem Verweis auf das neueste Objekt aktualisiert.
Lösung ist die Verwendung einer anderen Hilfsfunktion:
function makeHelper(obj)
{
return {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = makeHelper(obj);
}
}
Dies funktioniert, weil lokale Variablen im Funktionsumfang direkt sowie Funktionsargumentvariablen bei der Eingabe neue Kopien zugewiesen werden.
Eine ausführliche Diskussion finden Sie unter JavaScript-Fallstricke und -nutzung
Ein anderer Weg, der noch nicht erwähnt wurde, ist die Verwendung von Function.prototype.bind
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function(x) {
console.log('My value: ' + x);
}.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
AKTUALISIEREN
Wie von @squint und @mekdev hervorgehoben, erzielen Sie eine bessere Leistung, wenn Sie die Funktion zuerst außerhalb der Schleife erstellen und dann die Ergebnisse innerhalb der Schleife binden.
function log(x) {
console.log('My value: ' + x);
}
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = log.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Etwas spät zur Party, aber ich habe mich heute mit diesem Thema befasst und festgestellt, dass viele der Antworten nicht ganz auf die Frage eingehen, wie Javascript die Gültigkeitsbereiche behandelt, worauf es im Wesentlichen ankommt.
Wie viele andere erwähnt, besteht das Problem darin, dass die innere Funktion auf dieselbe i
Variable verweist. Warum erstellen wir also nicht einfach bei jeder Iteration eine neue lokale Variable und haben stattdessen die innere Funktion als Referenz?
//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 ausgegeben hat, der i
zugewiesen wurde, gibt jetzt jede innere Funktion nur den letzten Wert aus, der ilocal
zugewiesen wurde. Aber sollte nicht jede Iteration ihre eigene ilocal
?
Es stellt sich heraus, das ist das Problem. Jede Iteration hat den gleichen Gültigkeitsbereich, daher wird bei jeder Iteration nach der ersten ilocal
einfach überschrieben. Von MDN :
Wichtig: JavaScript hat keinen Blockumfang. Variablen, die mit einem Block eingeführt werden, beziehen sich auf die enthaltende Funktion oder das Skript, und die Auswirkungen ihrer Einstellung bleiben über den Block hinaus erhalten. Mit anderen Worten, Blockanweisungen führen keinen Geltungsbereich ein. Obwohl "eigenständige" Blöcke gültige Syntax sind, möchten Sie in JavaScript keine eigenständigen Blöcke verwenden, da sie nicht das tun, was Sie ihrer Meinung nach tun, wenn Sie der Meinung sind, dass sie solche Blöcke in C oder Java tun.
Zur Betonung nochmals betont:
JavaScript hat keinen Blockumfang. Variablen, die mit einem Block eingeführt werden, beziehen sich auf die enthaltende Funktion oder das Skript
Wir können dies sehen, indem Sie ilocal
ü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 aus diesem Grund ist dieser Fehler so schwierig. Obwohl Sie eine Variable erneut deklarieren, gibt Javascript keinen Fehler aus, und JSLint gibt nicht einmal eine Warnung aus. Dies ist auch der Grund, warum der beste Weg, dieses Problem zu lösen, darin besteht, Schließungen zu nutzen. Dies ist im Wesentlichen die Idee, dass innere Funktionen in Javascript auf äußere Variablen zugreifen können, da innere Bereiche äußere Bereiche "einschließen".
Dies bedeutet auch, dass innere Funktionen äußere Variablen "halten" und sie am Leben erhalten, auch wenn die äußere Funktion zurückkehrt. Um dies zu nutzen, erstellen und rufen wir eine Wrapper-Funktion auf, um einen neuen Gültigkeitsbereich zu ilocal
, ilocal
im neuen Gültigkeitsbereich zu deklarieren und eine innere Funktion zurückzugeben, 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 das Erstellen der inneren Funktion innerhalb einer Wrapper-Funktion erhält die innere Funktion eine private Umgebung, auf die nur sie zugreifen kann, ein "Abschluss". Jedes Mal, wenn wir die Wrapper-Funktion aufrufen, erstellen wir eine neue innere Funktion mit einer eigenen separaten Umgebung, um sicherzustellen, dass die ilocal
Variablen nicht kollidieren und sich nicht ü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 Mainstream können wir jetzt das neue Schlüsselwort let
, um Variablen mit Blockumfang 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.
Hier ist eine einfache Lösung, die forEach
(funktioniert bis IE9):
var funcs = [];
[0,1,2].forEach(function(i) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
})
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Drucke:
My value: 0 My value: 1 My value: 2
JavaScript-Funktionen schließen den Bereich, auf den sie bei der Deklaration zugreifen können, und behalten den Zugriff auf diesen Bereich, selbst wenn sich Variablen in diesem Bereich ändern.
var funcs = []
for (var i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
Jede Funktion im obigen Array wird über den globalen Bereich geschlossen (global, einfach weil dies der Bereich ist, in dem sie deklariert sind).
Später werden diese Funktionen aufgerufen, indem der aktuellste Wert von i
im globalen Bereich protokolliert wird. Das ist die Magie und Frustration der Schließung.
"JavaScript-Funktionen schließen sich über den Gültigkeitsbereich, in dem sie deklariert sind, und behalten den Zugriff auf diesen Gültigkeitsbereich, selbst wenn sich die Werte innerhalb dieses Gültigkeitsbereichs ändern."
Mit let
anstelle von var
dieses var
gelöst, indem bei jeder Ausführung der for
Schleife ein neuer Gültigkeitsbereich erstellt wird, der einen separaten Bereich für jede zu schließende Funktion erstellt. Verschiedene andere Techniken machen dasselbe mit zusätzlichen Funktionen.
var funcs = []
for (let i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
( let
macht Variablen mit Blockbereich statt Funktionsbereich. Blöcke werden durch geschweifte Klammern gekennzeichnet, aber im Fall der for-Schleife gilt die Initialisierungsvariable i
in unserem Fall als in den Klammern deklariert.)
Nun, das Problem ist, dass die Variable i
innerhalb jeder Ihrer anonymen Funktionen an dieselbe Variable außerhalb der Funktion gebunden ist.
Klassische Lösung: Verschlüsse
Sie möchten 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++) {
funcs[j](); // and now let's run each one to see
}
Da es in JavaScript - nur Funktionsumfang - keinen Blockbereich gibt, indem Sie die Funktionserstellung in eine neue Funktion einbetten, stellen Sie sicher, dass der Wert von "i" wie gewünscht bleibt.
2015 Lösung: forEach
Array.prototype.forEach
der relativ weit verbreiteten Funktion Array.prototype.forEach
(2015) ist es erwähnenswert, dass .forEach()
in solchen Situationen, in denen die Iteration hauptsächlich über eine Reihe von Werten erfolgt, eine saubere und natürliche Möglichkeit bietet, einen eindeutigen Abschluss zu erhalten jede Wiederholung. Vorausgesetzt, Sie haben eine Art Array mit Werten (DOM-Referenzen, Objekte, was auch immer) und das Problem, Callbacks für jedes Element einzurichten, können Sie folgendermaßen 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, seine eigene Schließung ist. Der an diesen Handler übergebene Parameter ist das für diesen bestimmten Schritt der Iteration spezifische Arrayelement. Wenn es in einem asynchronen Callback verwendet wird, kollidiert es nicht mit den anderen Callbacks, die an anderen Schritten der Iteration erstellt wurden.
Wenn Sie mit jQuery arbeiten, bietet Ihnen die Funktion $.each()
eine ähnliche Funktion.
ES6-Lösung: let
ECMAScript 6 (ES6), die neueste Version von JavaScript, wird nun in vielen Evergreen-Browsern und Backend-Systemen implementiert. Es gibt auch Transpiler wie Babel , die ES6 in ES5 konvertieren, um die Verwendung neuer Funktionen auf älteren Systemen zu ermöglichen.
ES6 führt neue let- und const
Schlüsselwörter ein, die sich anders als var
basierte Variablen unterscheiden. In einer Schleife mit einem let
basierten Index hat beispielsweise jede Iteration durch die Schleife einen neuen Wert von i
wobei jeder Wert innerhalb der Schleife liegt, sodass Ihr Code wie erwartet funktioniert. Es gibt viele Ressourcen, aber ich würde den Block-Scoping-Beitrag von 2ality als großartige Informationsquelle empfehlen.
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 die Unterstützung let
aber die obigen Fehler nicht beheben (sie erstellen nicht jedes Mal ein neues i
, sodass alle oben genannten Funktionen 3 protokollieren würden, wenn wir var
). Rand 14 hat es endlich geschafft.
Was Sie verstehen müssen, ist der Umfang der Variablen in Javascript basiert auf der Funktion. Dies ist ein wichtiger Unterschied, wenn Sie c # angeben, wenn Sie über einen Blockbereich verfügen. Das Kopieren der Variablen in eine für for wird funktionieren.
Das Einbetten in eine Funktion, die die Rückgabe der Funktion wie die Antwort von apphacker auswertet, wird den Trick erfüllen, da die Variable nun den Funktionsumfang hat.
Anstelle von var gibt es auch ein let-Schlüsselwort, das die Verwendung der Block-Regel erlaubt. In diesem Fall würde das Definieren einer Variablen innerhalb von for den Trick tun. Das let-Schlüsselwort ist jedoch aus Gründen der Kompatibilität keine praktische Lösung.
var funcs = {};
for (var i = 0; i < 3; i++) {
let index = i; //add this
funcs[i] = function() {
console.log("My value: " + index); //change to the copy
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
COUNTER EIN PRIMITIVES
Definieren wir Callback-Funktionen wie folgt:
// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
for (var i=0; i<2; i++) {
setTimeout(function() {
console.log(i);
});
}
}
test1();
// 2
// 2
Nach Ablauf des Timeouts werden 2 für beide gedruckt. Dies liegt daran, dass die Rückruffunktion auf den Wert basierend auf dem lexikalischen Gültigkeitsbereich zugreift , in dem die Funktion definiert wurde.
Um den Wert zu übergeben und zu erhalten, während der Rückruf definiert wurde, können wir eine closure erstellen , um den Wert zu erhalten, bevor der Rückruf aufgerufen wird. Dies kann wie folgt durchgeführt werden:
function test2() {
function sendRequest(i) {
setTimeout(function() {
console.log(i);
});
}
for (var i = 0; i < 2; i++) {
sendRequest(i);
}
}
test2();
// 1
// 2
Das Besondere daran: "Die Primitiven werden als Wert übergeben und kopiert. Wenn der Abschluss definiert ist, behalten sie den Wert aus der vorherigen Schleife."
GEGENSTAND EIN OBJEKT
Da Schließungen über Referenz auf übergeordnete Funktionsvariablen zugreifen können, würde sich dieser Ansatz von dem für Grundelemente unterscheiden.
// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
var index = { i: 0 };
for (index.i=0; index.i<2; index.i++) {
setTimeout(function() {
console.log('test3: ' + index.i);
});
}
}
test3();
// 2
// 2
Selbst wenn ein Abschluss für die als Objekt übergebene Variable erstellt wird, bleibt der Wert des Schleifenindex jedoch nicht erhalten. Dies soll zeigen, dass die Werte eines Objekts nicht kopiert werden, während auf sie über eine Referenz zugegriffen wird.
function test4() {
var index = { i: 0 };
function sendRequest(index, i) {
setTimeout(function() {
console.log('index: ' + index);
console.log('i: ' + i);
console.log(index[i]);
});
}
for (index.i=0; index.i<2; index.i++) {
sendRequest(index, index.i);
}
}
test4();
// index: { i: 2}
// 0
// undefined
// index: { i: 2}
// 1
// undefined
Mit neuen Funktionen von ES6 wird das Blockebenen-Scoping verwaltet
var funcs = [];
for (let i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (let j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Der Code in der OP-Frage wird let
anstelle von ersetzt var
.
Verstehe zuerst, was mit diesem Code falsch ist:
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Wenn hier das funcs[]
Array initialisiert wird, i
wird es inkrementiert, das funcs
Array wird initialisiert und die Größe des func
Arrays wird 3 i = 3,
. Wenn nun das funcs[j]()
aufgerufen wird, verwendet es wieder die Variable i
, die bereits auf 3 erhöht wurde.
Um dies zu lösen, haben wir viele Möglichkeiten. Im Folgenden sind zwei davon:
Wir können initialisieren
i
mitlet
oder eine neue Variable initialisierenindex
mitlet
und machen es gleichi
. Wenn der Anruf getätigt wird,index
wird er verwendet, und sein Umfang endet nach der Initialisierung. Und zum Anrufenindex
wird wieder initialisiert:var funcs = []; for (var i = 0; i < 3; i++) { let index = i; funcs[i] = function() { console.log("My value: " + index); }; } for (var j = 0; j < 3; j++) { funcs[j](); }
Eine andere Option kann die Einführung einer sein,
tempFunc
die die eigentliche Funktion zurückgibt:var funcs = []; function tempFunc(i){ return function(){ console.log("My value: " + i); }; } for (var i = 0; i < 3; i++) { funcs[i] = tempFunc(i); } for (var j = 0; j < 3; j++) { funcs[j](); }
Ich bevorzuge die forEach
Funktion, die eine eigene Schließung beim Erstellen eines Pseudo-Bereichs hat:
var funcs = [];
new Array(3).fill(0).forEach(function (_, i) { // creating a range
funcs[i] = function() {
// now i is safely incapsulated
console.log("My value: " + i);
};
});
for (var j = 0; j < 3; j++) {
funcs[j](); // 0, 1, 2
}
Das sieht hässlicher aus als Bereiche in anderen Sprachen, aber IMHO weniger monströs als andere Lösungen.
Ich bin überrascht, dass noch niemand vorgeschlagen hat, die forEach
Funktion zu verwenden, um lokale Variablen besser zu vermeiden. Tatsächlich verwende ich for(var i ...)
aus diesem Grund überhaupt keine Verwendung mehr.
[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3
// zur Verwendung forEach
anstelle von Map bearbeitet .
Nach dem Durchlesen verschiedener Lösungen möchte ich hinzufügen, dass der Grund für das Funktionieren dieser Lösungen darin besteht, sich auf das Konzept der Scope-Chain zu verlassen . So löst JavaScript eine Variable während der Ausführung auf.
- Jede Funktionsdefinition bildet einen Gültigkeitsbereich, der aus allen von
var
und deklarierten lokalen Variablen bestehtarguments
. - Wenn eine innere Funktion in einer anderen (äußeren) Funktion definiert ist, bildet diese eine Kette und wird während der Ausführung verwendet
- Wenn eine Funktion ausgeführt wird, wertet die Laufzeit Variablen durch Durchsuchen der Gültigkeitsbereichskette aus . Wenn eine Variable an einem bestimmten Punkt der Kette gefunden werden kann, wird sie nicht mehr gesucht und verwendet, andernfalls wird sie fortgesetzt, bis der globale Gültigkeitsbereich erreicht ist, zu dem die Variable gehört
window
.
Im ursprünglichen Code:
funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function inner() { // function inner's scope contains nothing
console.log("My value: " + i);
};
}
console.log(window.i) // test value 'i', print 3
Wenn funcs
ausgeführt, wird die Gültigkeitsbereichskette sein function inner -> global
. Da die Variable i
nicht gefunden werden kann function inner
(weder als deklariert var
noch als Argument übergeben wird), wird die Suche fortgesetzt, bis schließlich der Wert von i
im globalen Gültigkeitsbereich gefunden wird window.i
.
Wenn Sie es in eine äußere Funktion share definieren Sie entweder explizit eine share wie share oder verwenden Sie eine anonyme Funktion wie share :
funcs = {};
function outer(i) { // function outer's scope contains 'i'
return function inner() { // function inner, closure created
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = outer(i);
}
console.log(window.i) // print 3 still
Wenn funcs
ausgeführt wird, wird die Scope-Chain jetzt sein function inner -> function outer
. Diese Zeit i
ist im Gültigkeitsbereich der äußeren Funktion zu finden, der dreimal in der for-Schleife ausgeführt wird, wobei jedes Mal der Wert i
korrekt gebunden ist. Es wird nicht den Wert von verwendet, window.i
wenn innerlich ausgeführt wird.
Weitere Details finden Sie here
Sie enthält den üblichen Fehler bei der Erstellung von Closures in der Schleife als das, was wir hier haben, und auch, warum wir Closure und die Leistungsbetrachtung benötigen.
Sie können ein deklaratives Modul für Listen von Daten verwenden, z. B. query-js (*). In diesen Situationen finde ich einen deklarativen Ansatz weniger überraschend
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
Sie können dann Ihre zweite Schleife verwenden und das erwartete Ergebnis erhalten, oder Sie könnten es tun
funcs.iterate(function(f){ f(); });
(*) Ich bin der Autor von query-js und daher eher voreingenommen in Bezug auf die Verwendung. Nehmen Sie meine Worte daher nicht als Empfehlung für die genannte Bibliothek nur für den deklarativen Ansatz. :)
Verwenden Sie eine closure , dies würde Ihre zusätzliche Schleife reduzieren. Sie können es in einer einzigen for-Schleife machen:
var funcs = [];
for (var i = 0; i < 3; i++) {
(funcs[i] = function() {
console.log("My value: " + i);
})(i);
}
Viele Lösungen scheinen korrekt zu sein, erwähnen aber nicht, dass es sich Currying
um ein funktionales Designmuster für Situationen wie hier handelt. Je nach Browser 3-10 mal schneller als binden.
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
function curryShowValue(i) {
return function showValue() {
console.log("My value: " + i);
}
}
Sehen Sie die Leistungssteigerung in verschiedenen Browsern .