JavaScript Closure innerhalb von Schleifen – einfaches Praxisbeispiel

Lesezeit: 11 Minuten

JavaScript Closure innerhalb von Schleifen – einfaches
Nickf

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

Während ich möchte, dass es ausgibt:

Mein Wert: 0
Mein Wert: 1
Mein Wert: 2


Das gleiche Problem tritt auf, wenn die Verzögerung beim Ausführen 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, zB 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));
}

Es zeigt sich auch in for in und 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?

  • Du willst sicher nicht funcs ein Array sein, wenn Sie numerische Indizes verwenden? Nur ein Kopf hoch.

    – DanMan

    26. Juli 2013 um 11:12 Uhr

  • Das ist ein wirklich verwirrendes Problem. Dies Artikel hilft mir, es zu verstehen. Vielleicht hilft es auch anderen.

    – Benutzer3199690

    3. Mai 2014 um 15:38 Uhr

  • Eine weitere einfache und erklärte Lösung: 1) Verschachtelte Funktionen haben Zugriff auf den Geltungsbereich “über” ihnen; 2) ein Schließung Lösung… “Ein Abschluss ist eine Funktion, die Zugriff auf den übergeordneten Bereich hat, auch nachdem die übergeordnete Funktion geschlossen wurde”.

    – Peter Krauß

    17. Dezember 2014 um 1:22 Uhr


  • Verweisen Sie auf diesen Link für ein besseres Verständnis javascript.info/tutorial/advanced-functions

    – Saurabh Ahuja

    2. April 2015 um 12:01 Uhr


  • Im ES6ist eine triviale Lösung, die Variable zu deklarieren ich mit Lassendie auf den Schleifenkörper beschränkt ist.

    – Thomas Nikodym

    13. September 2016 um 20:34 Uhr

JavaScript Closure innerhalb von Schleifen – einfaches
Björn

Versuchen:

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

Bearbeiten (2014):

Persönlich denke ich, @Austs neuere Antwort zur Verwendung .bind ist der beste Weg, so etwas jetzt zu tun. Es gibt auch Lo-Bindestriche/Unterstriche _.partial wenn Sie nicht brauchen oder wollen bind‘S thisArg.

  • jede Erklärung über die }(i)); ?

    – aswzen

    6. April 2018 um 1:32 Uhr

  • @aswzen Ich denke, es geht vorbei i als Argument index zur Funktion.

    – Jetblau

    26. Juli 2018 um 22:01 Uhr

  • es erstellt tatsächlich einen lokalen Variablenindex.

    – Abhishek Singh

    15. März 2019 um 15:17 Uhr

  • Funktionsausdruck sofort aufrufen, auch bekannt als IIFE. (i) ist das Argument für den anonymen Funktionsausdruck, der sofort aufgerufen wird, und der Index wird von i gesetzt.

    – Eier

    14. April 2020 um 5:51 Uhr

  • Das mache ich heutzutage auch, ich mag auch Lo-Bindestriche/Unterstriche _.partial

    – Björn

    8. Dezember 2014 um 5:18 Uhr

  • .bind() wird mit ECMAScript 6-Funktionen weitgehend veraltet sein. Außerdem erzeugt dies tatsächlich zwei Funktionen pro Iteration. Zuerst die anonyme, dann die generierte von .bind(). Besser wäre es dann, es außerhalb der Schleife zu erstellen .bind() es hinein.

    Benutzer1106925

    28. Juni 2015 um 3:29 Uhr

  • @squint @mekdev – Ihr habt beide Recht. Mein erstes Beispiel wurde schnell geschrieben, um zu demonstrieren, wie bind wird genutzt. Ich habe ein weiteres Beispiel gemäß Ihren Vorschlägen hinzugefügt.

    – Aust

    29. Juni 2015 um 16:23 Uhr

  • Ich denke, anstatt Berechnungen über zwei O(n)-Schleifen zu verschwenden, tun Sie einfach for (var i = 0; i < 3; i++) { log.call(this, i); }

    – Benutzer2290820

    11. September 2015 um 12:14 Uhr


  • .bind() tut, was die akzeptierte Antwort vorschlägt PLUS spielt mit this.

    – nein

    8. Januar 2017 um 5:55 Uhr

1646905519 576 JavaScript Closure innerhalb von Schleifen – einfaches
neurosnap

Mit einem Sofort aufgerufener Funktionsausdruckdie einfachste und lesbarste Art, eine Indexvariable einzuschließen:

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);

}

Dies sendet den Iterator i in die anonyme Funktion, die wir definieren als index. Dadurch entsteht ein Abschluss, bei dem die Variable i wird für die spätere Verwendung in jeder asynchronen Funktionalität innerhalb des IIFE gespeichert.

  • Für eine bessere Lesbarkeit des Codes und um Verwirrung darüber zu vermeiden i was ist, würde ich den Funktionsparameter in umbenennen index.

    – Kyle Falconer

    10. Januar 2014 um 16:45 Uhr

  • Wie würden Sie diese Technik verwenden, um das Array zu definieren? Funktionen in der ursprünglichen Frage beschrieben?

    – Nico

    30. November 2014 um 13:17 Uhr

  • @Nico Auf die gleiche Weise wie in der ursprünglichen Frage gezeigt, außer dass Sie verwenden würden index anstatt i.

    – JLRishe

    31. März 2015 um 20:54 Uhr

  • @JLRishe var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }

    – Nico

    1. April 2015 um 9:22 Uhr


  • @Nico Im speziellen Fall von OP iterieren sie nur über Zahlen, daher wäre dies kein guter Fall .forEach()aber oft, wenn man mit einem Array beginnt, forEach() ist eine gute Wahl, wie: var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });

    – JLRishe

    1. April 2015 um 10:05 Uhr


JavaScript Closure innerhalb von Schleifen – einfaches
Gemeinschaft

Etwas spät zur Party, aber ich habe dieses Problem heute untersucht und festgestellt, dass viele der Antworten nicht vollständig darauf eingehen, wie Javascript Bereiche behandelt, worauf es im Wesentlichen hinausläuft.

Wie viele andere bereits erwähnt haben, besteht das Problem darin, dass die innere Funktion auf dasselbe verweist i Variable. Warum erstellen wir also nicht einfach bei jeder Iteration eine neue lokale Variable und lassen stattdessen die innere Funktion darauf verweisen?

//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 zuletzt zugewiesenen Wert ausgab ijetzt gibt jede innere Funktion nur den letzten zugewiesenen Wert aus ilocal. Aber sollte nicht jede Iteration ihre eigene haben ilocal?

Es stellt sich heraus, das ist das Problem. Jede Iteration teilt sich denselben Bereich, sodass jede Iteration nach der ersten nur überschrieben wird ilocal. Von MDN:

Wichtig: JavaScript hat keinen Blockbereich. Variablen, die mit einem Block eingeführt werden, sind auf die enthaltende Funktion oder das enthaltende Skript beschränkt, und die Auswirkungen ihrer Einstellung bleiben über den Block selbst hinaus bestehen. Mit anderen Worten, Blockanweisungen führen keinen Geltungsbereich ein. Obwohl “eigenständige” Blöcke eine gültige Syntax sind, sollten Sie keine eigenständigen Blöcke in JavaScript verwenden, da sie nicht das tun, was Sie denken, wenn Sie denken, dass sie so etwas wie solche Blöcke in C oder Java tun.

Wiederholt zur Betonung:

JavaScript hat keinen Blockbereich. Variablen, die mit einem Block eingeführt werden, sind auf die enthaltende Funktion oder das Skript beschränkt

Wir können dies sehen, indem wir überprüfen ilocal 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 knifflig. Auch wenn Sie eine Variable neu deklarieren, gibt Javascript keinen Fehler aus und JSLint gibt nicht einmal eine Warnung aus. Dies ist auch der Grund, warum der beste Weg, dies zu lösen, darin besteht, Closures zu nutzen, was im Wesentlichen die Idee ist, dass in Javascript innere Funktionen Zugriff auf äußere Variablen haben, weil innere Bereiche äußere Bereiche “einschließen”.

Schließungen

Dies bedeutet auch, dass innere Funktionen äußere Variablen “festhalten” und am Leben erhalten, selbst wenn die äußere Funktion zurückkehrt. Um dies zu nutzen, erstellen und rufen wir eine Wrapper-Funktion auf, um lediglich einen neuen Geltungsbereich zu deklarieren ilocal im neuen Geltungsbereich und geben eine innere Funktion zurück, die verwendet ilocal (mehr 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]();
}

Das Erstellen der inneren Funktion innerhalb einer Wrapper-Funktion gibt der inneren Funktion eine private Umgebung, auf die nur sie zugreifen kann, einen “Abschluss”. Daher erstellen wir jedes Mal, wenn wir die Wrapper-Funktion aufrufen, eine neue innere Funktion mit einer eigenen separaten Umgebung, um sicherzustellen, dass die ilocal Variablen kollidieren nicht und überschreiben sich gegenseitig. 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

Da ES6 jetzt Mainstream ist, können wir jetzt das Neue nutzen let Schlüsselwort zum Erstellen von blockbezogenen Variablen:

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

  • Für eine bessere Lesbarkeit des Codes und um Verwirrung darüber zu vermeiden i was ist, würde ich den Funktionsparameter in umbenennen index.

    – Kyle Falconer

    10. Januar 2014 um 16:45 Uhr

  • Wie würden Sie diese Technik verwenden, um das Array zu definieren? Funktionen in der ursprünglichen Frage beschrieben?

    – Nico

    30. November 2014 um 13:17 Uhr

  • @Nico Auf die gleiche Weise wie in der ursprünglichen Frage gezeigt, außer dass Sie verwenden würden index anstatt i.

    – JLRishe

    31. März 2015 um 20:54 Uhr

  • @JLRishe var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }

    – Nico

    1. April 2015 um 9:22 Uhr


  • @Nico Im speziellen Fall von OP iterieren sie nur über Zahlen, daher wäre dies kein guter Fall .forEach()aber oft, wenn man mit einem Array beginnt, forEach() ist eine gute Wahl, wie: var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });

    – JLRishe

    1. April 2015 um 10:05 Uhr


1646905520 627 JavaScript Closure innerhalb von Schleifen – einfaches
henser

Da ES6 jetzt weithin unterstützt wird, hat sich die beste Antwort auf diese Frage geändert. ES6 bietet die let und const Schlüsselwörter für genau diesen Umstand. Anstatt mit Verschlüssen herumzuspielen, können wir einfach verwenden let um eine Loop-Scope-Variable wie folgt zu setzen:

var funcs = [];

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

val zeigt dann auf ein Objekt, das spezifisch für diese bestimmte Wendung der Schleife ist, und gibt den korrekten Wert ohne die zusätzliche Abschlussnotation zurück. Dies vereinfacht dieses Problem offensichtlich erheblich.

const ist ähnlich wie let mit der zusätzlichen Einschränkung, dass der Variablenname nach der anfänglichen Zuweisung nicht an eine neue Referenz gebunden werden kann.

Die Browserunterstützung ist jetzt für diejenigen da, die auf die neuesten Browserversionen abzielen. const/let werden derzeit in 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 Build-Tools wie Babel nutzen. Ein funktionierendes Beispiel sehen Sie hier: http://jsfiddle.net/ben336/rbU4t/2/

Dokumente hier:

Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 unterstützt werden let aber verstehe das obige falsch (sie erstellen keine neue i jedes Mal, also würden alle oben genannten Funktionen 3 protokollieren, wie sie es tun würden, wenn wir sie verwenden würden var). Edge 14 macht es endlich richtig.

  • Leider wird „let“ immer noch nicht vollständig unterstützt, insbesondere auf Mobilgeräten. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

    – MattC

    23. Februar 2016 um 17:47 Uhr

  • Ab Juni ’16, Lassen wird in allen gängigen Browserversionen außer iOS Safari, Opera Mini und Safari 9 unterstützt. Evergreen-Browser unterstützen dies. Babel wird es korrekt transpilieren, um das erwartete Verhalten beizubehalten, ohne dass der Modus für hohe Kompatibilität eingeschaltet ist.

    – Dan

    22. Juni 2016 um 10:18 Uhr


  • @DanPantry ja, es wird Zeit für ein Update 🙂 Aktualisiert, um den aktuellen Stand der Dinge besser widerzuspiegeln, einschließlich Hinzufügen einer Erwähnung von const, Doc-Links und besserer Kompatibilitätsinformationen.

    – Ben McCormick

    27. Juni 2016 um 14:24 Uhr


  • Ist das nicht der Grund, warum wir babel verwenden, um unseren Code zu transpilieren, damit Browser, die ES6/7 nicht unterstützen, verstehen können, was vor sich geht?

    – Pixel 67

    19. März 2018 um 15:56 Uhr

987400cookie-checkJavaScript Closure innerhalb von Schleifen – einfaches Praxisbeispiel

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy