stackoverflow - JavaScript-Verschluss in Schleifen-einfaches praktisches Beispiel




stackoverflow javascript (20)

Wir werden prüfen, was tatsächlich passiert , wenn Sie erklären , varund leteins 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:

  1. 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 von ioder window.iim Konsolenfenster überprüfen.
  2. Die anonyme Funktion, die Sie deklariert haben, ruft den Wert in der Funktion nur auf, wenn Sie die Funktionen aufrufen.
  3. Wenn Sie die Funktion aufrufen, console.log("My value: " + i)übernimmt sie den Wert aus dem GlobalObjekt 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 BlockSie nun das Objekt, Sie werden sehen, dass 'i' dort definiert ist, und das Merkwürdige ist, dass für jede Funktion der Wert if iunterschiedlich 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 BlockEbene Variable , die nur für die gleiche Funktion zur Verfügung steht only.That der Grund ist, werden wir Wert immer iunterschiedlich 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);};
}

http://jsfiddle.net/7P6EN/


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

jsFiddle Demo

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]()
}

( letmacht Variablen mit Blockbereich statt Funktionsbereich. Blöcke werden durch geschweifte Klammern gekennzeichnet, aber im Fall der for-Schleife gilt die Initialisierungsvariable iin 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 letanstelle 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, iwird es inkrementiert, das funcsArray wird initialisiert und die Größe des funcArrays 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:

  1. Wir können initialisieren imit letoder eine neue Variable initialisieren indexmit letund machen es gleich i. Wenn der Anruf getätigt wird, indexwird er verwendet, und sein Umfang endet nach der Initialisierung. Und zum Anrufen indexwird 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]();                        
    }
    
  2. Eine andere Option kann die Einführung einer sein, tempFuncdie 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 forEachFunktion, 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 forEachFunktion 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 forEachanstelle 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 varund deklarierten lokalen Variablen besteht arguments.
  • 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 funcsausgeführt, wird die Gültigkeitsbereichskette sein function inner -> global. Da die Variable inicht gefunden werden kann function inner(weder als deklariert varnoch als Argument übergeben wird), wird die Suche fortgesetzt, bis schließlich der Wert von iim 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 funcsausgeführt wird, wird die Scope-Chain jetzt sein function inner -> function outer. Diese Zeit iist im Gültigkeitsbereich der äußeren Funktion zu finden, der dreimal in der for-Schleife ausgeführt wird, wobei jedes Mal der Wert ikorrekt gebunden ist. Es wird nicht den Wert von verwendet, window.iwenn 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 Curryingum 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 .





closures