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.

672voto

Aadit M Shah Punkte 70322

Anmerkung der Redaktion: Alle Funktionen in JavaScript sind Schließungen, wie in diesem Abschnitt erläutert Beitrag . Wir sind jedoch nur daran interessiert, eine Teilmenge dieser Funktionen zu identifizieren, die interessant von einem theoretischen Standpunkt aus. Von nun an wird jede Bezugnahme auf das Wort Verschluss bezieht sich auf diese Untergruppe von Funktionen, sofern nicht anders angegeben.

Eine einfache Erklärung für die Schließungen:

  1. Nehmen Sie eine Funktion. Nennen wir sie F.
  2. Listen Sie alle Variablen von F auf.
  3. Die Variablen können zweierlei Art sein:
    1. Lokale Variablen (gebundene Variablen)
    2. Nichtlokale Variablen (freie Variablen)
  4. Wenn F keine freien Variablen hat, kann es kein Abschluss sein.
  5. Wenn F irgendwelche freien Variablen hat (die in a übergeordneter Bereich von F) dann:
    1. Es darf nur einen übergeordneten Bereich von F geben, zu dem a freie Variable gebunden ist.
    2. Wenn F referenziert von außerhalb dass übergeordneten Bereich, dann wird er zu einem Abschluss für dass freie Variable.
    3. Das freie Variable wird als Aufwärtswert des Abschlusses F bezeichnet.

Nun wollen wir damit herausfinden, wer Schließungen verwendet und wer nicht (zur Erklärung habe ich die Funktionen benannt):

Fall 1: Das Programm deines Freundes

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

In dem obigen Programm gibt es zwei Funktionen: f y g . Mal sehen, ob es sich um Schließungen handelt:

については f :

  1. Listen Sie die Variablen auf:
    1. i2 es un . variabel.
    2. i es un kostenlos variabel.
    3. setTimeout es un kostenlos variabel.
    4. g es un . variabel.
    5. console es un kostenlos variabel.
  2. Finden Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. i es gebunden in den globalen Bereich.
    2. setTimeout es gebunden in den globalen Bereich.
    3. console es gebunden in den globalen Bereich.
  3. In welchem Bereich ist die Funktion referenziert ? Die globale Reichweite .
    1. Daher i ist nicht zugemacht por f .
    2. Daher setTimeout ist nicht zugemacht por f .
    3. Daher console ist nicht zugemacht por f .

Somit ist die Funktion f ist kein Abschluss.

については g :

  1. Listen Sie die Variablen auf:
    1. console es un kostenlos variabel.
    2. i2 es un kostenlos variabel.
  2. Finden Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. console es gebunden in den globalen Bereich.
    2. i2 es gebunden zum Umfang von f .
  3. In welchem Bereich ist die Funktion referenziert ? Die Umfang der setTimeout .
    1. Daher console ist nicht zugemacht por g .
    2. Daher i2 es zugemacht por g .

Somit ist die Funktion g ist ein Abschluss für die freie Variable i2 (das ist ein Aufwertungswert für g ) wenn Es ist referenziert von innen setTimeout .

Schlecht für Sie: Ihr Freund verwendet einen Verschluss. Die innere Funktion ist eine Schließung.

Fall 2: Ihr Programm

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

In dem obigen Programm gibt es zwei Funktionen: f y g . Mal sehen, ob es sich um Schließungen handelt:

については f :

  1. Listen Sie die Variablen auf:
    1. i2 es un . variabel.
    2. g es un . variabel.
    3. console es un kostenlos variabel.
  2. Finden Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. console es gebunden in den globalen Bereich.
  3. In welchem Bereich ist die Funktion referenziert ? Die globale Reichweite .
    1. Daher console ist nicht zugemacht por f .

Somit ist die Funktion f ist kein Abschluss.

については g :

  1. Listen Sie die Variablen auf:
    1. console es un kostenlos variabel.
    2. i2 es un kostenlos variabel.
  2. Finden Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. console es gebunden in den globalen Bereich.
    2. i2 es gebunden zum Umfang von f .
  3. In welchem Bereich ist die Funktion referenziert ? Die Umfang der setTimeout .
    1. Daher console ist nicht zugemacht por g .
    2. Daher i2 es zugemacht por g .

Somit ist die Funktion g ist ein Abschluss für die freie Variable i2 (das ist ein Aufwertungswert für g ) wenn Es ist referenziert von innen setTimeout .

Gut für Sie: Sie verwenden einen Verschluss. Die innere Funktion ist eine Schließung.

Sie und Ihr Freund verwenden also beide Verschlüsse. Hören Sie auf zu streiten. Ich hoffe, ich konnte Ihnen beiden das Konzept der Verschlüsse und deren Erkennung erläutern.

Bearbeiten: Eine einfache Erklärung, warum alle Funktionen Schließungen sind (Credits @Peter):

Betrachten wir zunächst das folgende Programm (es ist das Kontrolle ) :

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}
  1. Wir wissen, dass sowohl lexicalScope y regularFunction sind keine Schließungen aus der obigen Definition .
  2. Wenn wir das Programm ausführen wir erwarten message alarmiert zu werden denn regularFunction kein Abschluss ist (d. h. er hat Zugang zu todos die Variablen in ihrem übergeordneten Bereich - einschließlich message ).
  3. Wenn wir das Programm ausführen wir beobachten dass message ist tatsächlich alarmiert.

Betrachten wir als nächstes das folgende Programm (es ist das alternativ ) :

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}
  1. Wir wissen, dass nur closureFunction ist eine Schließung aus der obigen Definition .
  2. Wenn wir das Programm ausführen wir erwarten message nicht alarmiert zu werden denn closureFunction ein Abschluss ist (d. h. er hat nur Zugriff auf alle seine nicht-lokale Variablen unter der Zeitpunkt, zu dem die Funktion erstellt wird ( siehe diese Antwort ) - dies schließt nicht ein message ).
  3. Wenn wir das Programm ausführen wir beobachten dass message tatsächlich alarmiert wird.

Was können wir daraus schließen?

  1. JavaScript-Interpreter behandeln Closures nicht anders als andere Funktionen.
  2. Jede Funktion hat ihre Umfangskette mit sich bringen. Verschlüsse haben keine getrennt Referenzierungsumgebung.
  3. Ein Abschluss ist genau wie jede andere Funktion. Wir nennen sie nur Schließungen, wenn sie referenziert in einem Rahmen außerhalb der Bereich, zu dem sie gehören denn Dies ist ein interessanter Fall.

98voto

kev Punkte 145226

Nach Angaben der closure Definition:

Ein "Abschluss" ist ein Ausdruck (in der Regel eine Funktion), der folgende Eigenschaften haben kann freie Variablen zusammen mit einer Umwelt die diese Variablen bindet (die den Ausdruck "abschließt").

Sie verwenden closure wenn Sie eine Funktion definieren, die eine Variable verwendet, die außerhalb der Funktion definiert ist. (wir nennen die Variable a freie Variable ).
Sie alle verwenden closure (auch im 1. Beispiel).

55voto

brillout Punkte 8542

Kurz und bündig Javascript-Schließungen eine Funktion ermöglichen, die Zugriff auf eine Variable das ist deklariert in einer lexikalischen Elternfunktion .

Lassen Sie sich eine genauere Erklärung geben. Um Closures zu verstehen, ist es wichtig zu wissen, wie JavaScript Variablen in den Gültigkeitsbereich bringt.

Geltungsbereiche

In JavaScript werden Geltungsbereiche mit Funktionen definiert. Jede Funktion definiert einen neuen Bereich.

Betrachten Sie das folgende Beispiel;

function f()
{//begin of scope f
  var foo='hello'; //foo is declared in scope f
  for(var i=0;i<2;i++){//i is declared in scope f
     //the for loop is not a function, therefore we are still in scope f
     var bar = 'Am I accessible?';//bar is declared in scope f
     console.log(foo);
  }
  console.log(i);
  console.log(bar);
}//end of scope f

Aufruf von f druckt

hello
hello
2
Am I Accessible?

Betrachten wir nun den Fall, dass wir eine Funktion haben g innerhalb einer anderen Funktion definiert f .

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

Wir werden anrufen f el lexikalische Muttergesellschaft de g . Wie bereits erklärt, haben wir jetzt 2 Bereiche; der Bereich f und der Umfang g .

Aber ein Bereich liegt "innerhalb" des anderen Bereichs, ist also der Bereich der untergeordneten Funktion Teil des Bereichs der übergeordneten Funktion? Was geschieht mit den Variablen, die im Bereich der übergeordneten Funktion deklariert sind; kann ich aus dem Bereich der untergeordneten Funktion auf sie zugreifen? Genau an dieser Stelle kommen Schließungen ins Spiel.

Schließungen

In JavaScript wird die Funktion g kann nicht nur auf alle im Geltungsbereich deklarierten Variablen zugreifen g sondern auch auf alle im Bereich der übergeordneten Funktion deklarierten Variablen zugreifen f .

Beachten Sie Folgendes;

function f()//lexical parent function
{//begin of scope f
  var foo='hello'; //foo declared in scope f
  function g()
  {//being of scope g
    var bar='bla'; //bar declared in scope g
    console.log(foo);
  }//end of scope g
  g();
  console.log(bar);
}//end of scope f

Aufruf von f druckt

hello
undefined

Schauen wir uns die Zeile an console.log(foo); . An diesem Punkt sind wir in Reichweite g und wir versuchen, auf die Variable foo die im Geltungsbereich deklariert ist f . Aber wie bereits erwähnt, können wir auf jede Variable zugreifen, die in einer lexikalischen Elternfunktion deklariert ist, was hier der Fall ist; g ist der lexikalische Elternteil von f . Daher hello gedruckt wird.
Schauen wir uns nun die Zeile console.log(bar); . An diesem Punkt sind wir in Reichweite f und wir versuchen, auf die Variable bar die im Geltungsbereich deklariert ist g . bar nicht im aktuellen Geltungsbereich deklariert ist und die Funktion g ist nicht der Elternteil von f daher bar ist undefiniert

Tatsächlich können wir auch auf die Variablen zugreifen, die im Bereich einer lexikalischen "Großelternfunktion" deklariert sind. Wenn es also eine Funktion gibt h die in der Funktion g

function f()
{//begin of scope f
  function g()
  {//being of scope g
    function h()
    {//being of scope h
      /*...*/
    }//end of scope h
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

entonces h auf alle Variablen zugreifen kann, die im Geltungsbereich der Funktion h , g et f . Dies geschieht mit Verschlüsse . In JavaScript Verschlüsse ermöglicht den Zugriff auf jede Variable, die in der lexikalischen übergeordneten Funktion, in der lexikalischen großelterlichen Funktion, in der lexikalischen großelterlichen Funktion usw. deklariert ist. Dies kann man als eine Umfangskette ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... bis zur letzten übergeordneten Funktion, die keinen lexikalischen Vorgänger hat.

Das Fensterobjekt

Die Kette endet jedoch nicht bei der letzten übergeordneten Funktion. Es gibt noch einen speziellen Bereich; die globale Reichweite . Jede Variable, die nicht in einer Funktion deklariert ist, gilt als im globalen Bereich deklariert. Der globale Bereich hat zwei Besonderheiten;

  • jede im globalen Bereich deklarierte Variable ist zugänglich überall
  • die im globalen Bereich deklarierten Variablen entsprechen den Eigenschaften der window Objekt.

Es gibt also genau zwei Möglichkeiten, eine Variable zu deklarieren foo im globalen Bereich; entweder indem sie nicht in einer Funktion deklariert wird oder indem die Eigenschaft foo des Fensterobjekts.

Beide Versuche verwenden Verschlüsse

Nachdem Sie nun eine ausführlichere Erklärung gelesen haben, dürfte klar sein, dass beide Lösungen Verschlüsse verwenden. Aber um sicher zu gehen, wollen wir einen Beweis antreten.

Lassen Sie uns eine neue Programmiersprache erstellen: JavaScript-No-Closure. Wie der Name schon sagt, ist JavaScript-No-Closure identisch mit JavaScript, außer dass es keine Closures unterstützt.

Mit anderen Worten;

var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello

Schauen wir mal, was bei der ersten Lösung mit JavaScript-No-Closure passiert;

for(var i = 0; i < 10; i++) {
  (function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2); //i2 is undefined in JavaScript-No-Closure 
    }, 1000)
  })();
}

Daher wird Folgendes gedruckt undefined 10 Mal in JavaScript-No-Closure.

Bei der ersten Lösung wird daher die Schließung verwendet.

Schauen wir uns die zweite Lösung an;

for(var i = 0; i < 10; i++) {
  setTimeout((function(i2){
    return function() {
        console.log(i2); //i2 is undefined in JavaScript-No-Closure
    }
  })(i), 1000);
}

Daher wird Folgendes gedruckt undefined 10 Mal in JavaScript-No-Closure.

Beide Lösungen verwenden Verschlüsse.

Bearbeiten: Es wird davon ausgegangen, dass diese 3 Codeschnipsel nicht im globalen Bereich definiert sind. Andernfalls werden die Variablen foo y i wäre an den window Objekt und daher über die window Objekt sowohl in JavaScript als auch in JavaScript-No-Closure.

23voto

Erik Reppen Punkte 4527

Ich war noch nie zufrieden mit der Art und Weise, wie jemand dies erklärt.

Der Schlüssel zum Verständnis von Closures ist, zu verstehen, wie JS ohne Closures aussehen würde.

Ohne Verschlüsse würde dies einen Fehler auslösen

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

Sobald outerFunc hat in einer imaginären Schließung deaktivierten Version von JavaScript zurückgegeben, würde der Verweis auf outerVar Garbage Collect und weg verlassen nichts dort für die innere func zu verweisen.

Closures sind im Wesentlichen die speziellen Regeln, die es ermöglichen, dass diese Variablen existieren, wenn eine innere Funktion auf die Variablen einer äußeren Funktion verweist. Mit Closures werden die Variablen, auf die verwiesen wird, auch dann beibehalten, wenn die äußere Funktion beendet oder "geschlossen" ist, wenn Sie sich den Punkt merken können.

Selbst mit Closures funktioniert der Lebenszyklus lokaler Variablen in einer Funktion ohne innere Funktionen, die auf ihre lokalen Variablen verweisen, genauso wie in einer Version ohne Closures. Wenn die Funktion beendet ist, werden die lokalen Variablen in den Müll geworfen.

Sobald Sie einen Verweis in einer inneren func auf eine äußere var haben, ist es jedoch wie ein Türpfosten in den Weg der Garbage Collection für diese referenzierten vars gesetzt wird.

Eine vielleicht genauere Art, Closures zu betrachten, ist, dass die innere Funktion im Grunde den inneren Bereich als ihre eigene Bereichsbezeichnung verwendet.

Aber der Kontext, auf den verwiesen wird, ist in der Tat beständig, nicht wie eine Momentaufnahme. Das wiederholte Auslösen einer zurückgegebenen inneren Funktion, die die lokale Variable einer äußeren Funktion inkrementiert und protokolliert, führt dazu, dass immer höhere Werte gemeldet werden.

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2

17voto

Jon Punkte 411383

Sie verwenden beide Verschlüsse.

Ich gehe mit dem Wikipedia-Definition hier:

In der Informatik ist ein Abschluss (auch lexikalischer Abschluss oder Funktionsabschluss Schließung) eine Funktion oder ein Verweis auf eine Funktion zusammen mit einer Referenzierungsumgebung - eine Tabelle, die eine Referenz auf jede der nicht-lokalen Variablen (auch freie Variablen genannt) dieser Funktion. Eine Closure - anders als ein einfacher Funktionszeiger - erlaubt einer Funktion den Zugriff auf auf diese nicht-lokalen Variablen zugreifen, auch wenn sie außerhalb ihrer unmittelbaren lexikalischen Bereichs aufgerufen wird.

Der Versuch Ihres Freundes verwendet eindeutig die Variable i die nicht lokal ist, indem man ihren Wert nimmt und eine Kopie anfertigt, die im lokalen i2 .

Ihr eigener Versuch scheitert i (die sich am Aufrufort im Geltungsbereich befindet) an eine anonyme Funktion als Argument übergeben. Dies ist noch kein Abschluss, aber dann gibt diese Funktion eine andere Funktion zurück, die auf dieselbe i2 . Da innerhalb der inneren anonymen Funktion i2 nicht lokal ist, wird ein Abschluss erstellt.

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