581 Stimmen

JavaScript-Schließungen vs. anonyme Funktionen

Ein Freund von mir und ich diskutieren gerade darüber, was ein Abschluss in JS ist und was nicht. Wir wollen nur sicherstellen, dass wir es wirklich richtig verstehen.

Nehmen wir dieses Beispiel. Wir haben eine Zählschleife und wollen die Zählervariable verzögert auf der Konsole ausgeben. Deshalb verwenden wir setTimeout y Verschlüsse um den Wert der Zählervariablen zu erfassen, um sicherzustellen, dass sie nicht N-mal den Wert N ausgibt.

Die falsche Lösung ohne Verschlüsse oder irgendetwas in der Nähe von Verschlüsse wäre:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

was natürlich den 10-fachen Wert von i nach der Schleife, nämlich 10.

Das war sein Versuch:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

Druck von 0 bis 9 wie erwartet.

Ich habe ihm gesagt, dass er nicht mit einem Verschluss zu erfassen i aber er besteht darauf, dass er es ist. Ich habe bewiesen, dass er keine Verschlüsse indem man den Körper der for-Schleife in eine andere setTimeout (Übergabe seiner anonymen Funktion an setTimeout ), wobei wieder 10 mal 10 gedruckt wird. Dasselbe gilt, wenn ich seine Funktion in einer var und führen sie aus nach die Schleife und druckt ebenfalls 10 mal 10. Mein Argument ist also, dass er hat nicht wirklich erfassen den Wert von i und macht seine Version no einen Abschluss.

Das war mein Versuch:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

Ich erfasse also i (genannt i2 im Rahmen der Schließung), aber jetzt habe ich return eine andere Funktion und geben diese weiter. In meinem Fall fängt die an setTimeout übergebene Funktion wirklich i .

Wer verwendet nun Verschlüsse und wer nicht?

Beachten Sie, dass beide Lösungen verzögert 0 bis 9 auf der Konsole ausgeben, also das ursprüngliche Problem lösen, aber wir wollen verstehen, welche dieser beiden Lösungen verwendet Verschlüsse um dies zu erreichen.

13voto

Andrew D. Punkte 7890

Sie und Ihr Freund verwenden beide Verschlüsse:

Ein Abschluss ist eine besondere Art von Objekt, das zwei Dinge kombiniert: eine Funktion und die Umgebung, in der diese Funktion erstellt wurde. Die Umgebung besteht aus allen lokalen Variablen, die zum Zeitpunkt der Erstellung der Closure im Gültigkeitsbereich waren.

MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

In der Code-Funktion deines Freundes function(){ console.log(i2); } definiert innerhalb der Schließung der anonymen Funktion function(){ var i2 = i; ... und kann lokale Variablen lesen/schreiben i2 .

In Ihrer Code-Funktion function(){ console.log(i2); } definiert innerhalb der Schließung der Funktion function(i2){ return ... und kann lokal wertvolle Daten lesen/schreiben i2 (in diesem Fall als Parameter deklariert).

In beiden Fällen funktionieren function(){ console.log(i2); } dann übergegangen in setTimeout .

Ein anderes Äquivalent (aber mit geringerer Speichernutzung) ist:

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}

11voto

Andries Punkte 1527

Schließung

Ein Abschluss ist weder eine Funktion noch ein Ausdruck. Sie ist als eine Art "Schnappschuss" von den verwendeten Variablen außerhalb des Funktionsbereichs zu sehen und wird innerhalb der Funktion verwendet. Grammatikalisch sollte man sagen: "Nimm die Schließung der Variablen".

Nochmals mit anderen Worten: Eine Schließung ist eine Kopie des relevanten Kontexts von Variablen, von denen die Funktion abhängt.

Noch einmal (naïf): Ein Closure hat Zugriff auf Variablen, die nicht als Parameter übergeben werden.

Beachten Sie, dass diese funktionalen Konzepte stark von der von Ihnen verwendeten Programmiersprache / Umgebung abhängen. In JavaScript hängt die Schließung vom lexikalischen Scoping ab (was in den meisten C-Sprachen der Fall ist).

Die Rückgabe einer Funktion ist also meist die Rückgabe einer anonymen/unbenannten Funktion. Wenn die Funktion auf Variablen zugreift, die nicht als Parameter übergeben wurden, und sich in ihrem (lexikalischen) Anwendungsbereich befindet, wurde eine Schließung vorgenommen.

Nun zu Ihren Beispielen:

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

Alle verwenden Verschlüsse. Verwechseln Sie nicht den Ausführungszeitpunkt mit Schließungen. Wenn der "Schnappschuss" der Closures zum falschen Zeitpunkt gemacht wird, können die Werte unerwartet sein, aber es wird auf jeden Fall eine Closure gemacht!

10voto

Ja͢ck Punkte 165747

Betrachten wir beide Möglichkeiten:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

Deklariert und führt sofort eine anonyme Funktion aus, die setTimeout() in ihrem eigenen Kontext. Der aktuelle Wert von i wird erhalten, indem eine Kopie in i2 zuerst; es funktioniert wegen der sofortigen Ausführung.

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

Deklariert einen Ausführungskontext für die innere Funktion, wobei der aktuelle Wert von i wird erhalten in i2 Auch bei diesem Ansatz wird die sofortige Ausführung verwendet, um den Wert zu erhalten.

Wichtig

Es sollte erwähnt werden, dass die Laufsemantik bei beiden Ansätzen NICHT die gleiche ist; Ihre innere Funktion wird an setTimeout() während seine innere Funktion setTimeout() selbst.

Beide Codes werden in einen anderen Code eingeschlossen setTimeout() beweist nicht, dass nur der zweite Ansatz Verschlüsse verwendet, es ist einfach nicht das Gleiche, um damit zu beginnen.

Schlussfolgerung

Beide Methoden verwenden Verschlüsse, so dass es auf den persönlichen Geschmack ankommt; der zweite Ansatz ist leichter zu "verschieben" oder zu verallgemeinern.

9voto

Nat Darke Punkte 841

Ich habe dies vor einer Weile geschrieben, um mich daran zu erinnern, was ein Abschluss ist und wie er in JS funktioniert.

Eine Closure ist eine Funktion, die beim Aufruf den Bereich verwendet, in dem sie deklariert wurde, und nicht den Bereich, in dem sie aufgerufen wurde. In JavaScript verhalten sich alle Funktionen auf diese Weise. Variablenwerte in einem Bereich bleiben bestehen, solange es eine Funktion gibt, die noch auf sie verweist. Die Ausnahme von dieser Regel ist "this", das sich auf das Objekt bezieht, in dem sich die Funktion befindet, wenn sie aufgerufen wird.

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2'

6voto

Ramesh Punkte 12657

Bei näherer Betrachtung sieht es so aus, als würden Sie beide einen Verschluss verwenden.

Im Fall deines Freundes, i wird innerhalb der anonymen Funktion 1 aufgerufen und i2 wird in der anonymen Funktion 2 aufgerufen, wobei die console.log vorhanden ist.

In Ihrem Fall haben Sie Zugriff auf i2 innerhalb einer anonymen Funktion, bei der console.log vorhanden ist. Hinzufügen einer debugger; Anweisung vor console.log und in den Chrome-Entwicklertools wird unter "Scope variables" angegeben, in welchem Bereich sich die Variable befindet.

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