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".

169voto

Ben McCormick Punkte 24158

Da ES6 nun weitgehend unterstützt wird, hat sich die beste Antwort auf diese Frage geändert. ES6 bietet die let y const Schlüsselwörter für genau diesen Sachverhalt. Anstatt sich mit Schließungen herumzuschlagen, können wir einfach verwenden let um eine Schleifenbereichsvariable wie diese zu setzen:

var funcs = [];

for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val verweist dann auf ein Objekt, das spezifisch für diese bestimmte Runde der Schleife ist, und gibt den korrekten Wert ohne die zusätzliche Closure-Notation zurück. Dies vereinfacht dieses Problem natürlich erheblich.

const ist vergleichbar mit let mit der zusätzlichen Einschränkung, dass der Variablenname nach der ersten Zuweisung nicht in eine neue Referenz umgewandelt werden kann.

Die Browserunterstützung für die neuesten Versionen von Browsern ist jetzt verfügbar. const / let werden derzeit von 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 die Vorteile von Build-Tools wie Babel nutzen. Sie können ein funktionierendes Beispiel hier sehen: http://jsfiddle.net/ben336/rbU4t/2/

Dokumente hier:

Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 Folgendes unterstützen let aber die obigen Angaben sind falsch (sie erstellen keine neue i jedes Mal, so dass alle obigen Funktionen 3 protokollieren würden, wie sie es tun würden, wenn wir var ). Edge 14 macht es endlich richtig.

98voto

Darren Clark Punkte 2853

Man könnte auch sagen, dass die i in Ihrer Funktion ist zum Zeitpunkt der Ausführung der Funktion gebunden, nicht zum Zeitpunkt der Erstellung der Funktion.

Wenn Sie den Abschluss erstellen, i ist ein Verweis auf die Variable, die im äußeren Bereich definiert ist, und nicht eine Kopie davon, wie sie beim Erstellen der Schließung war. Sie wird zum Zeitpunkt der Ausführung ausgewertet.

Die meisten anderen Antworten bieten Möglichkeiten zur Umgehung, indem eine andere Variable erstellt wird, die den Wert nicht für Sie ändert.

Ich dachte nur, ich füge der Klarheit halber eine Erklärung hinzu. Als Lösung würde ich persönlich die von Harto wählen, da sie von den Antworten hier am selbsterklärendsten ist. Jeder der geposteten Codes wird funktionieren, aber ich würde mich für eine Closure Factory entscheiden, anstatt einen Haufen Kommentare zu schreiben, um zu erklären, warum ich eine neue Variable deklariere (Freddy und 1800) oder eine seltsame eingebettete Closure-Syntax habe (apphacker).

80voto

eglasius Punkte 35447

Was Sie verstehen müssen, ist der Umfang der Variablen in Javascript ist auf der Grundlage der Funktion. Dies ist ein wichtiger Unterschied als sagen c#, wo Sie Block-Bereich haben, und kopieren Sie einfach die Variable zu einem innerhalb der für wird funktionieren.

Wenn Sie es in eine Funktion einschließen, die evaluiert wird und die Funktion zurückgibt, wie in der Antwort von apphacker, ist das kein Problem, da die Variable jetzt den Funktionsumfang hat.

Es gibt auch ein let-Schlüsselwort anstelle von var, das die Verwendung der Blockbereichsregel ermöglichen würde. In diesem Fall würde die Definition einer Variablen innerhalb der for-Anweisung genügen. Allerdings ist das let-Schlüsselwort 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]();
}

66voto

Boann Punkte 46908

Hier ist eine weitere Variante der Technik, ähnlich der von Bjorn (apphacker), bei der Sie den Variablenwert innerhalb der Funktion zuweisen können, anstatt ihn als Parameter zu übergeben, was manchmal übersichtlicher sein kann:

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

Beachten Sie, dass unabhängig von der Technik, die Sie verwenden, die index Variable wird zu einer Art statischer Variable, die an die zurückgegebene Kopie der inneren Funktion gebunden ist. Das heißt, dass Änderungen an ihrem Wert zwischen Aufrufen erhalten bleiben. Das kann sehr praktisch sein.

64voto

Dies beschreibt den häufigsten Fehler bei der Verwendung von Closures in JavaScript.

Eine Funktion definiert eine neue Umgebung

Bedenken Sie:

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

Für jede Zeit makeCounter aufgerufen wird, {counter: 0} führt dazu, dass ein neues Objekt erstellt wird. Außerdem wird eine neue Kopie von obj wird ebenfalls erstellt, um auf das neue Objekt zu verweisen. So, counter1 y counter2 unabhängig voneinander sind.

Schließungen in Schleifen

Die Verwendung eines Verschlusses in einer Schleife ist knifflig.

Bedenken Sie:

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] y counters[1] sind no unabhängig. Tatsächlich arbeiten sie mit denselben obj !

Dies liegt daran, dass es nur eine Kopie von obj über alle Iterationen der Schleife verteilt, vielleicht aus Leistungsgründen. Auch wenn {counter: 0} erzeugt bei jeder Iteration ein neues Objekt, die gleiche Kopie von obj wird einfach mit einer Referenz auf das neueste Objekt aktualisiert.

Die Lösung ist, eine andere Hilfsfunktion zu verwenden:

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 direkt im Funktionsbereich sowie Funktionsargumentvariablen bei der Eingabe neue Kopien zugewiesen werden.

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