3254 Stimmen

JavaScript-Schließung in Schleifen - einfaches praktisches Beispiel

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

Sie gibt dies aus:

Mein Wert: 3
Mein Wert: 3
Mein Wert: 3

Ich hingegen möchte, 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));
}

Das zeigt sich auch in for in y 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?

62 Stimmen

Sind Sie sicher, dass Sie nicht wollen, dass funcs ein Array zu sein, wenn Sie numerische Indizes verwenden? Nur eine Vorwarnung.

33 Stimmen

Das ist ein wirklich verwirrendes Problem. Diese Artikel hilft mir, ihn zu verstehen . Vielleicht hilft es ja auch anderen.

4 Stimmen

Eine weitere einfache und gut erklärte Lösung: 1) Verschachtelte Funktionen Zugriff auf den Bereich "über" ihnen haben ; 2) a Verschluss Lösung ... "Eine Schließung ist eine Funktion, die Zugriff auf den übergeordneten Bereich hat, auch nachdem die übergeordnete Funktion geschlossen wurde".

53voto

Kemal Dağ Punkte 2705

Die einfachste Lösung wäre,

Anstatt zu verwenden:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

die "2" meldet, für 3 Mal. Das liegt daran, dass anonyme Funktionen, die in der for-Schleife erstellt werden, denselben Abschluss haben, und in diesem Abschluss wird der Wert von i ist die gleiche. Verwenden Sie dies, um ein gemeinsames Schließen zu verhindern:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

Die Idee dahinter ist, den gesamten Körper der for-Schleife mit einer IIFE (Immediately-Invoked Function Expression) und die Übergabe von new_i als Parameter und die Erfassung als i . Da die anonyme Funktion sofort ausgeführt wird, ist die i Wert ist für jede Funktion, die innerhalb der anonymen Funktion definiert ist, unterschiedlich.

Diese Lösung scheint für alle derartigen Probleme geeignet zu sein, da sie nur minimale Änderungen am ursprünglichen Code erfordert, der von diesem Problem betroffen ist. In der Tat, dies ist durch Design, sollte es nicht ein Problem überhaupt sein!

35voto

Daryl Punkte 3144

Hier ist eine einfache Lösung, bei der forEach (funktioniert wieder mit 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

34voto

yilmazburk Punkte 887

Versuchen Sie diese kürzere Version

  • keine Reihe

  • keine zusätzliche for-Schleife

    for (var i = 0; i < 3; i++) { createfunc(i)(); }

    function createfunc(i) { return function(){console.log("My value: " + i);}; }

http://jsfiddle.net/7P6EN/

31voto

Travis J Punkte 78877

Das Hauptproblem mit dem vom OP gezeigten Code ist, dass i wird erst in der zweiten Schleife gelesen. Zur Veranschaulichung stellen Sie sich vor, dass innerhalb des Codes ein Fehler auftritt

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 eigentlich erst auf, wenn funcs[someIndex] ausgeführt wird () . Unter Anwendung derselben Logik sollte es offensichtlich sein, dass der Wert von i wird auch bis zu diesem Zeitpunkt nicht gesammelt. Sobald die ursprüngliche Schleife beendet ist, i++ bringt i auf den Wert von 3 was zu der Bedingung führt i < 3 scheitert und die Schleife endet. Zu diesem Zeitpunkt, i est 3 und wenn also funcs[someIndex]() verwendet wird, und i ausgewertet wird, ist es 3 - jedes Mal.

Um dies zu überwinden, müssen Sie Folgendes bewerten i wenn sie angetroffen wird. Beachten Sie, dass dies bereits geschehen ist in Form von funcs[i] (wo es 3 eindeutige Indizes gibt). Es gibt mehrere Möglichkeiten, diesen Wert zu erfassen. Eine davon ist, ihn als Parameter an eine Funktion zu übergeben, die hier bereits auf verschiedene Weise gezeigt wird.

Eine andere Möglichkeit besteht darin, ein Funktionsobjekt zu konstruieren, das in der Lage ist, die Variable zu schließen. Das lässt sich folgendermaßen bewerkstelligen

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

25voto

Costa Michailidis Punkte 7035

JavaScript-Funktionen "verschließen" den Bereich, auf den sie bei der Deklaration Zugriff haben, und behalten den Zugriff auf diesen Bereich, auch 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 schließt über den globalen Bereich ab (global, weil das der Bereich ist, in dem sie deklariert sind).

Später werden diese Funktionen aufgerufen und protokollieren den aktuellsten Wert von i im globalen Bereich. Das ist der Zauber - und die Frustration - des Abschlusses.

"JavaScript-Funktionen schließen über den Bereich, in dem sie deklariert sind, und behalten den Zugriff auf diesen Bereich, auch wenn sich Variablenwerte innerhalb dieses Bereichs ändern."

Verwendung von let anstelle von var löst dieses Problem, indem es jedes Mal einen neuen Bereich erstellt, wenn die for Schleife durchläuft und für jede Funktion einen separaten Bereich schafft, über den sie sich schließen kann. Verschiedene andere Techniken tun das Gleiche 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 blockbezogen. Blöcke werden durch geschweifte Klammern gekennzeichnet, aber im Fall der for-Schleife die Initialisierungsvariable, i wird in unserem Fall als in geschweiften Klammern deklariert betrachtet).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X