JavaScript-Schließungen im Vergleich zu anonymen Funktionen


562

Ein Freund von mir und ich diskutieren derzeit, was eine Schließung 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 möchten die Zählervariable verzögert auf der Konsole drucken. Daher verwenden wir setTimeoutund Verschlüsse , um den Wert der Zählervariablen zu erfassen und sicherzustellen, dass sie nicht das N-fache des Werts N druckt.

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

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

das druckt natürlich das 10-fache des Wertes von inach der Schleife, nämlich 10.

Also war sein Versuch:

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

wie erwartet 0 bis 9 drucken.

Ich sagte ihm, dass er keinen Verschluss benutzt , um zu fangen i, aber er besteht darauf, dass er es ist. Ich habe bewiesen, dass er keine Verschlüsse verwendet, indem er den for-Schleifenkörper in einen anderen gelegt hat setTimeout(indem er seine anonyme Funktion an übergeben hat setTimeout) und erneut 10 mal 10 gedruckt hat. Gleiches gilt, wenn ich seine Funktion in a speichere varund sie nach der Schleife ausführe und auch 10 mal 10 drucke. Mein Argument ist also, dass er den Wert von nicht wirklich erfassti und seine Version nicht zu einem Abschluss macht.

Mein Versuch war:

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

Also ich capture i(benannt i2innerhalb des Verschlusses), aber jetzt habe ich zurückgeben eine andere Funktion und diese um passieren. In meinem Fall erfasst die an setTimeout übergebene Funktion wirklich i.

Wer benutzt nun Verschlüsse und wer nicht?

Beachten Sie, dass beide Lösungen verzögert 0 bis 9 auf der Konsole drucken, sodass sie das ursprüngliche Problem lösen. Wir möchten jedoch verstehen, welche dieser beiden Lösungen Verschlüsse verwendet , um dies zu erreichen.



1
@leemes: Siehe meinen Ninja-Schnitt für einen zweiten Link.
Blender

2
Wir haben gerade einen Deal gemacht:
Derjenige

1
@leemes - Sie beide verwenden Verschlüsse. Sie haben beide zwei Funktionen ausgeführt - eine äußere und eine innere Funktion; und Ihre beiden inneren Funktionen sind Verschlüsse. Alle Ihre Funktionen sind Lambdas ( anonyme Funktionen ). Lesen Sie meine Antwort für die Details.
Aadit M Shah

1
@blesh - Ich habe keine Ahnung, was ein modifizierter Verschluss ist. Ich sehe, dass Ihr Link auf C # -Code verweist. Werden geänderte Verschlüsse von JavaScript unterstützt?
Aadit M Shah

Antworten:


650

Anmerkung des Herausgebers: Alle Funktionen in JavaScript sind Schließungen, wie in diesem Beitrag erläutert . Wir sind jedoch nur daran interessiert, eine Teilmenge dieser Funktionen zu identifizieren, die aus theoretischer Sicht interessant sind . Sofern nicht anders angegeben, bezieht sich jede Bezugnahme auf das Wort Schließen auf diese Teilmenge von Funktionen.

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

  1. Nehmen Sie eine Funktion. Nennen wir es F.
  2. Listen Sie alle Variablen von F auf.
  3. Es gibt zwei Arten von Variablen:
    1. Lokale Variablen (gebundene Variablen)
    2. Nicht lokale Variablen (freie Variablen)
  4. Wenn F keine freien Variablen hat, kann es kein Abschluss sein.
  5. Wenn F freie Variablen hat (die in einem übergeordneten Bereich von F definiert sind), dann:
    1. Es darf nur einen übergeordneten Bereich von F geben, an den eine freie Variable gebunden ist.
    2. Wenn von außerhalb dieses übergeordneten Bereichs auf F verwiesen wird , wird es zu einem Abschluss für diese freie Variable.
    3. Diese freie Variable wird als Aufwert des Verschlusses F bezeichnet.

Verwenden wir dies nun, um herauszufinden, wer Verschlüsse verwendet und wer nicht (zur Erklärung habe ich die Funktionen benannt):

Fall 1: Das Programm Ihres Freundes

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

Im obigen Programm gibt es zwei Funktionen: fund g. Mal sehen, ob es sich um Verschlüsse handelt:

Für f:

  1. Listen Sie die Variablen auf:
    1. i2ist eine lokale Variable.
    2. iist eine freie Variable.
    3. setTimeoutist eine freie Variable.
    4. gist eine lokale Variable.
    5. consoleist eine freie Variable.
  2. Suchen Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. iist an den globalen Geltungsbereich gebunden .
    2. setTimeoutist an den globalen Geltungsbereich gebunden .
    3. consoleist an den globalen Geltungsbereich gebunden .
  3. In welchem ​​Bereich wird auf die Funktion verwiesen ? Der globale Geltungsbereich .
    1. Daher iwird nicht geschlossen über durch f.
    2. Daher setTimeoutwird nicht geschlossen über durch f.
    3. Daher consolewird nicht geschlossen über durch f.

Somit ist die Funktion fkein Abschluss.

Für g:

  1. Listen Sie die Variablen auf:
    1. consoleist eine freie Variable.
    2. i2ist eine freie Variable.
  2. Suchen Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. consoleist an den globalen Geltungsbereich gebunden .
    2. i2ist an den Geltungsbereich von gebundenf .
  3. In welchem ​​Bereich wird auf die Funktion verwiesen ? Der Umfang vonsetTimeout .
    1. Daher consolewird nicht geschlossen über durch g.
    2. Daher i2wird geschlossen über durch g.

Somit ist die Funktion gist ein Verschluss für die freie Variable i2(der ein für aufzuwerten ist g) , wenn es referenziert von innen setTimeout.

Schlecht für dich: Dein Freund benutzt einen Verschluss. Die innere Funktion ist ein Verschluss.

Fall 2: Ihr Programm

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

Im obigen Programm gibt es zwei Funktionen: fund g. Mal sehen, ob es sich um Verschlüsse handelt:

Für f:

  1. Listen Sie die Variablen auf:
    1. i2ist eine lokale Variable.
    2. gist eine lokale Variable.
    3. consoleist eine freie Variable.
  2. Suchen Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. consoleist an den globalen Geltungsbereich gebunden .
  3. In welchem ​​Bereich wird auf die Funktion verwiesen ? Der globale Geltungsbereich .
    1. Daher consolewird nicht geschlossen über durch f.

Somit ist die Funktion fkein Abschluss.

Für g:

  1. Listen Sie die Variablen auf:
    1. consoleist eine freie Variable.
    2. i2ist eine freie Variable.
  2. Suchen Sie den übergeordneten Bereich, an den jede freie Variable gebunden ist:
    1. consoleist an den globalen Geltungsbereich gebunden .
    2. i2ist an den Geltungsbereich von gebundenf .
  3. In welchem ​​Bereich wird auf die Funktion verwiesen ? Der Umfang vonsetTimeout .
    1. Daher consolewird nicht geschlossen über durch g.
    2. Daher i2wird geschlossen über durch g.

Somit ist die Funktion gist ein Verschluss für die freie Variable i2(der ein für aufzuwerten ist g) , wenn es referenziert von innen setTimeout.

Gut für Sie: Sie verwenden einen Verschluss. Die innere Funktion ist ein Verschluss.

Sie und Ihr Freund verwenden also Verschlüsse. Hört auf zu streiten. Ich hoffe, ich habe das Konzept der Verschlüsse geklärt und wie ich sie für Sie beide identifizieren kann.

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

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

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 beides lexicalScopeund regularFunctionkeine Verschlüsse aus der obigen Definition sind .
  2. Wenn wir das Programm ausführen, erwarten wir, dass wir message benachrichtigt werden, da regularFunction es sich nicht um einen Abschluss handelt (dh es hat Zugriff auf alle Variablen in seinem übergeordneten Bereich - einschließlich message).
  3. Wenn wir das Programm ausführen, stellen wir fest, dass dies messagetatsächlich alarmiert ist.

Als nächstes betrachten wir das folgende Programm (es ist die Alternative ):

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 closureFunctionein Abschluss aus der obigen Definition ist .
  2. Wenn wir das Programm ausführen erwarten wir message nicht gewarnt werden , weil closureFunction ein Verschluss (dh es hat nur Zugriff auf alle seine nicht-lokalen Variablen in der Zeit wird die Funktion erstellt ( siehe diese Antwort ) - dies schließt nicht message).
  3. Wenn wir das Programm ausführen, stellen wir fest, dass messagees tatsächlich alarmiert wird.

Was schließen wir daraus?

  1. JavaScript-Interpreter behandeln Closures nicht anders als andere Funktionen.
  2. Jede Funktion trägt ihre Scope-Kette mit sich. Closures haben keine separate Referenzierungsumgebung.
  3. Ein Verschluss ist wie jede andere Funktion. Wir nennen sie nur Schließungen , wenn sie referenziert in einem Bereich außerhalb des Umfangs, der sie angehören , weil dies ein interessanter Fall ist.

40
Akzeptiert, weil Sie sehr detailliert vorgehen und sehr schön erklären, was los ist. Und schließlich habe ich jetzt besser verstanden, was ein Abschluss ist, oder besser gesagt: wie die variable Bindung in JS funktioniert.
Leemes

3
In Fall 1 sagen Sie, dass dies gim Bereich von ausgeführt setTimeoutwird, in Fall 2 sagen Sie, dass dies fim globalen Bereich ausgeführt wird. Sie befinden sich beide in setTimeout. Was ist also der Unterschied?
Rosscj2533

9
Geben Sie bitte Ihre Quellen dafür an. Ich habe noch nie eine Definition gesehen, bei der eine Funktion ein Abschluss sein könnte, wenn sie in einem Bereich aufgerufen wird, aber nicht in einem anderen. Daher scheint diese Definition eine Teilmenge der allgemeineren Definition zu sein, an die ich gewöhnt bin (siehe kevs Antwort ), bei der ein Abschluss ein Abschluss ist, unabhängig vom Umfang, in dem er aufgerufen wird, oder selbst wenn er niemals aufgerufen wird!
Briguy37

11
@AaditMShah Ich stimme Ihnen zu, was ein Abschluss ist, aber Sie sprechen so, als ob es einen Unterschied zwischen regulären Funktionen und Abschlüssen in JavaScript gibt. Es gibt keinen Unterschied; Intern enthält jede Funktion einen Verweis auf die bestimmte Bereichskette, in der sie erstellt wurde. Die JS-Engine betrachtet dies nicht als einen anderen Fall. Eine komplizierte Checkliste ist nicht erforderlich. Sie müssen nur wissen, dass jedes Funktionsobjekt einen lexikalischen Gültigkeitsbereich hat. Die Tatsache, dass Variablen / Eigenschaften global verfügbar sind, macht die Funktion nicht weniger zu einem Abschluss (es ist nur ein nutzloser Fall).
Peter

13
@ Peter - Weißt du was, du bist richtig. Es gibt keinen Unterschied zwischen einer regulären Funktion und einem Verschluss. Ich habe einen Test durchgeführt, um dies zu beweisen, und dies führt zu Ihren Gunsten: Hier ist die Kontrolle und hier ist die Alternative . Was Sie sagen, macht Sinn. Der JavaScript-Interpreter muss eine spezielle Buchhaltung für Schließungen durchführen. Sie sind einfach Nebenprodukte einer lexikalischen Sprache mit erstklassigen Funktionen. Mein Wissen beschränkte sich auf das, was ich las (was falsch war). Danke, dass du mich korrigiert hast. Ich werde meine Antwort aktualisieren, um dasselbe widerzuspiegeln.
Aadit M Shah

96

Nach der closureDefinition:

Ein "Abschluss" ist ein Ausdruck (normalerweise eine Funktion), der freie Variablen zusammen mit einer Umgebung haben kann , die diese Variablen bindet (die den Ausdruck "schließt").

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


1
Wie verwendet die dritte Version eine außerhalb der Funktion definierte Variable?
Jon

1
@Jon die zurückgegebene Funktion verwenden, i2die außerhalb definiert ist.
Kev

1
@kev Sie verwenden Closure, wenn Sie eine Funktion definieren, die eine Variable verwendet, die außerhalb der Funktion definiert ist. In "Fall 1: Das Programm Ihres Freundes" von "Aadit M Shah" lautet die Antwort "Funktion f". eine Schließung? es verwendet das i (Variable, die außerhalb der Funktion definiert ist). Bezieht sich der globale Geltungsbereich auf einen Bestimmer?
Interna-in


54

Kurz gesagt, mit Javascript Closures kann eine Funktion auf eine Variable zugreifen , die in einer lexikalisch übergeordneten Funktion deklariert ist .

Sehen wir uns eine detailliertere Erklärung an. Um Abschlüsse zu verstehen, ist es wichtig zu verstehen, wie JavaScript Variablen abdeckt.

Geltungsbereich

In JavaScript werden Bereiche 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

Aufrufen von f-Drucken

hello
hello
2
Am I Accessible?

Betrachten wir nun den Fall, dass eine Funktion gin einer anderen Funktion definiert ist f.

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

Wir werden fdas lexikalische Elternteil von nennen g. Wie zuvor erklärt, haben wir jetzt 2 Bereiche; den Umfang fund den Umfang g.

Ein Bereich liegt jedoch "innerhalb" des anderen Bereichs. Ist der Bereich der untergeordneten Funktion Teil des Bereichs der übergeordneten Funktion? Was passiert mit den Variablen, die im Bereich der übergeordneten Funktion deklariert sind? Kann ich über den Bereich der untergeordneten Funktion auf sie zugreifen? Genau hier setzen Verschlüsse an.

Verschlüsse

In JavaScript kann die Funktion gnicht nur auf Variablen zugreifen, die im Gültigkeitsbereich deklariert sind, gsondern auch auf Variablen, die im Gültigkeitsbereich der übergeordneten Funktion deklariert sind f.

Betrachten 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

Aufrufen von f-Drucken

hello
undefined

Schauen wir uns die Linie an console.log(foo);. Zu diesem Zeitpunkt befinden wir uns im Gültigkeitsbereich gund versuchen, auf die fooim Gültigkeitsbereich deklarierte Variable zuzugreifen f. Wie bereits erwähnt, können wir auf jede Variable zugreifen, die in einer lexikalischen übergeordneten Funktion deklariert ist, was hier der Fall ist. gist das lexikalische Elternteil von f. Daher hellowird gedruckt.
Schauen wir uns jetzt die Linie an console.log(bar);. Zu diesem Zeitpunkt befinden wir uns im Gültigkeitsbereich fund versuchen, auf die barim Gültigkeitsbereich deklarierte Variable zuzugreifen g. barwird im aktuellen Bereich nicht deklariert und die Funktion gist nicht das übergeordnete Element von f, ist daher barundefiniert

Tatsächlich können wir auch auf die Variablen zugreifen, die im Rahmen einer lexikalischen "Grand Parent" -Funktion deklariert wurden. Daher, wenn hinnerhalb der Funktion eine Funktion definiert wäreg

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

dann him Rahmen der Funktion deklariert alle Variablen zuzugreifen wäre in der Lage h, gund f. Dies geschieht mit Verschlüssen . In JavaScript- Schließungen können wir auf alle Variablen zugreifen, die in der lexikalischen übergeordneten Funktion, in der lexikalischen Großelternfunktion, in der lexikalischen Großelternfunktion usw. deklariert sind. Dies kann als Bereichskette angesehen werden . scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... bis zur letzten übergeordneten Funktion, die kein lexikalisches übergeordnetes Element hat.

Das Fensterobjekt

Tatsächlich stoppt die Kette nicht bei der letzten übergeordneten Funktion. Es gibt noch einen besonderen Bereich; der globale Umfang . Jede Variable, die nicht in einer Funktion deklariert ist, gilt als im globalen Bereich deklariert. Der globale Geltungsbereich hat zwei Besonderheiten;

  • Auf jede im globalen Bereich deklarierte Variable kann überall zugegriffen werden
  • Die im globalen Bereich deklarierten Variablen entsprechen den Eigenschaften des windowObjekts.

Daher gibt es genau zwei Möglichkeiten, eine Variable fooim globalen Bereich zu deklarieren . entweder indem Sie es nicht in einer Funktion deklarieren oder indem Sie die Eigenschaft foodes Fensterobjekts festlegen.

Bei beiden Versuchen werden Verschlüsse verwendet

Nachdem Sie eine detailliertere Erklärung gelesen haben, kann es offensichtlich sein, dass beide Lösungen Verschlüsse verwenden. Aber um sicher zu gehen, machen wir einen Beweis.

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

Mit anderen Worten;

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

Okay, mal sehen, was mit 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 dies undefined10 Mal in JavaScript-No-Closure gedruckt .

Daher verwendet die erste Lösung den Verschluss.

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 dies undefined10 Mal in JavaScript-No-Closure gedruckt .

Beide Lösungen verwenden Verschlüsse.

Bearbeiten: Es wird davon ausgegangen, dass diese 3 Codefragmente nicht im globalen Bereich definiert sind. Andernfalls wären die Variablen foound ian das windowObjekt gebunden und daher über das windowObjekt sowohl in JavaScript als auch in JavaScript-No-Closure zugänglich .


Warum sollte iundefiniert sein? Sie beziehen sich nur auf den übergeordneten Bereich, der weiterhin gültig ist, wenn keine Abschlüsse vorhanden waren.
Leemes

aus dem gleichen Grund wie foo ist in JavaScript-No-Closure undefiniert. <code> i </ code> ist in JavaScript nicht undefiniert, dank einer Funktion in JavaScript, die den Zugriff auf Variablen ermöglicht, die im lexikalischen übergeordneten Element definiert sind. Diese Funktion wird als Schließen bezeichnet.
Brillout

Sie haben den Unterschied zwischen der Bezugnahme auf bereits definierte und freie Variablen nicht verstanden. In Closures definieren wir freie Variablen, die im äußeren Kontext gebunden werden müssen. In Ihrem Code stellen Sie gerade zu dem Zeitpunkt ein i2 , izu dem Sie Ihre Funktion definieren. Dies macht iKEINE freie Variable. Trotzdem betrachten wir Ihre Funktion als Abschluss, aber ohne freie Variable ist das der Punkt.
Leemes

2
@leemes, ich stimme zu. Und im Vergleich zur akzeptierten Antwort zeigt dies nicht wirklich, was tatsächlich vor sich geht. :)
Abel

3
Ich denke, dies ist die beste Antwort, um Verschlüsse allgemein und einfach zu erklären und dann auf den spezifischen Anwendungsfall einzugehen. Vielen Dank!
Tim Peterson

22

Ich war noch nie glücklich darüber, wie jemand dies erklärt.

Der Schlüssel zum Verständnis von Schließungen besteht darin, zu verstehen, wie JS ohne Schließungen 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 in einer imaginären Version von JavaScript mit deaktiviertem Abschluss zurückgekehrt ist, wird der Verweis auf OuterVar als Müll gesammelt und ist nicht mehr vorhanden, sodass die innere Funktion darauf verweisen kann.

Closures sind im Wesentlichen die speziellen Regeln, die diese Variablen aktivieren und ermöglichen, wenn eine innere Funktion auf die Variablen einer äußeren Funktion verweist. Bei Schließungen bleiben die referenzierten Variablen auch nach Ausführung der äußeren Funktion oder "geschlossen" erhalten, wenn dies Ihnen hilft, sich an den Punkt zu erinnern.

Selbst bei Verschlüssen funktioniert der Lebenszyklus lokaler Vars in einer Funktion ohne innere Funktionen, die auf die lokalen Verweise verweisen, genauso wie in einer Version ohne Verschluss. Wenn die Funktion beendet ist, erhalten die Einheimischen Müll gesammelt.

Sobald Sie in einer inneren Funktion einen Verweis auf eine äußere Variable haben, ist es jedoch so, als würde ein Türpfosten der Speicherbereinigung für die referenzierten Variablen im Wege stehen.

Eine vielleicht genauere Art, Verschlüsse zu betrachten, besteht darin, dass die innere Funktion im Grunde genommen den inneren Bereich als ihre eigene Bereichsfernung verwendet.

Der Kontext, auf den verwiesen wird, ist jedoch tatsächlich persistent und nicht wie ein Schnappschuss. Durch wiederholtes Auslösen einer zurückgegebenen inneren Funktion, die die lokale Variable einer äußeren Funktion weiter erhöht und protokolliert, werden immer höhere Werte angezeigt.

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

Damit haben Sie Recht mit dem "Schnappschuss" (ich denke, Sie beziehen sich auf meine Antwort). Ich suchte nach einem Wort, das das Verhalten anzeigen würde. In Ihrem Beispiel kann dies als "Hotlink" -Verschlusskonstruktion angesehen werden. Wenn man den Abschluss als Parameter in der inneren Funktion abfängt, kann man sagen, dass er sich wie ein 'Schnappschuss' verhält. Aber ich stimme zu, missbrauchte Wörter bringen nur Verwirrung in das Thema. Wenn Sie Vorschläge dazu haben, werde ich meine Antwort aktualisieren.
Andries

Es kann bei der Erklärung hilfreich sein, wenn Sie der inneren Funktion eine benannte Funktion geben.
Phillip Senn

Ohne Schließungen würde ein Fehler angezeigt, da Sie versuchen, eine nicht vorhandene Variable zu verwenden.
Juan Mendes

Hmm ... guter Punkt. Hat das Verweisen auf eine undefinierte Variable jemals keinen Fehler ausgelöst, da sie letztendlich als Eigenschaft für das globale Objekt angezeigt wird, oder verwechsle ich die Zuweisung zu undefinierten Variablen?
Erik Reppen

17

Sie verwenden beide Verschlüsse.

Ich gehe hier mit der Wikipedia-Definition :

In der Informatik ist ein Abschluss (auch lexikalischer Abschluss oder Funktionsabschluss) eine Funktion oder Referenz auf eine Funktion zusammen mit einer Referenzierungsumgebung - eine Tabelle, in der ein Verweis auf jede der nicht lokalen Variablen (auch als freie Variablen bezeichnet) dieser Funktion gespeichert ist . Ein Abschluss ermöglicht einer Funktion im Gegensatz zu einem einfachen Funktionszeiger den Zugriff auf diese nicht lokalen Variablen, selbst wenn sie außerhalb ihres unmittelbaren lexikalischen Bereichs aufgerufen wird.

Der Versuch Ihres Freundes verwendet eindeutig die Variable i, die nicht lokal ist, indem er ihren Wert nimmt und eine Kopie erstellt, um sie im lokalen zu speichern i2.

Ihr eigener Versuch iwird als Argument an eine anonyme Funktion übergeben (die sich an der Aufrufstelle befindet). Dies ist bisher kein Abschluss, aber dann gibt diese Funktion eine andere Funktion zurück, die auf dieselbe verweist i2. Da es sich bei der inneren anonymen Funktion i2nicht um eine lokale handelt, wird ein Abschluss erstellt.


Ja, aber ich denke, der Punkt ist, wie er es macht. Er kopiert einfach inach i2, definiert dann eine Logik und führt diese Funktion aus. Wenn ich es nicht sofort ausführen würde, sondern es in einer Variablen speichern und nach der Schleife ausführen würde, würde es 10 drucken, nicht wahr? Also hat es mich nicht erfasst.
Leemes

6
@leemes: Es hat iganz gut erfasst . Das Verhalten, das Sie beschreiben, ist kein Ergebnis von Schließung oder Nichtschließung. Dies ist darauf zurückzuführen, dass die geschlossene Variable in der Zwischenzeit geändert wurde. Sie tun dasselbe mit einer anderen Syntax, indem Sie sofort eine Funktion aufrufen und ials Argument übergeben (das den aktuellen Wert sofort kopiert). Wenn Sie Ihre eigenen setTimeoutin eine andere setTimeoutstecken, wird dasselbe passieren.
Jon

13

Sie und Ihr Freund verwenden beide Verschlüsse:

Ein Abschluss ist eine spezielle 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 des Abschlusses im Gültigkeitsbereich waren.

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

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

In Ihrem Code wird die Funktion function(){ console.log(i2); }innerhalb des Funktionsabschlusses definiert function(i2){ return ...und kann lokal wertvoll lesen / schreiben i2(in diesem Fall als Parameter deklariert).

In beiden Fällen wird die Funktion function(){ console.log(i2); }dann übergeben setTimeout.

Ein weiteres Äquivalent (jedoch mit geringerer Speicherauslastung) ist:

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

1
Ich verstehe nicht, warum Ihre Lösung im Vergleich zur Lösung meines Freundes "schneller und mit weniger Speicherauslastung ist". Könnten Sie das näher erläutern?
Brillout

3
In Ihrer Lösung erstellen Sie 20 Funktionsobjekte (2 Objekte in jeder Schleife: 2x10 = 20). Gleiches Ergebnis in Lösung Ihres Freundes. In "meiner" Lösung werden nur 11 Funktionsobjekte erstellt: 1 vor für Schleife und 10 "innerhalb" - 1 + 1x10 = 11. Infolgedessen weniger Speicherbedarf und höhere Geschwindigkeit.
Andrew D.

1
Theoretisch wäre das wahr. In der Praxis auch: Siehe diesen JSPerf-Benchmark: jsperf.com/closure-vs-name-function-in-a-loop/2
Rob W

10

Schließung

Ein Abschluss ist keine Funktion und kein Ausdruck. Es muss als eine Art "Momentaufnahme" der verwendeten Variablen außerhalb des Funktionsumfangs und innerhalb der Funktion betrachtet werden. Grammatisch sollte man sagen: "Schließen Sie die Variablen".

Nochmals mit anderen Worten: Ein Abschluss ist eine Kopie des relevanten Kontextes von Variablen, von denen die Funktion abhängt.

Noch einmal (naiv): Ein Abschluss hat Zugriff auf Variablen, die nicht als Parameter übergeben werden.

Beachten Sie, dass diese Funktionskonzepte stark von der von Ihnen verwendeten Programmiersprache / -umgebung abhängen. In JavaScript hängt der Abschluss vom lexikalischen Umfang ab (was in den meisten C-Sprachen der Fall ist).

Wenn Sie also eine Funktion zurückgeben, wird meistens eine anonyme / unbenannte Funktion zurückgegeben. Wenn die Funktion auf Variablen zugreift, die nicht als Parameter übergeben wurden und innerhalb ihres (lexikalischen) Bereichs liegen, wurde ein Abschluss vorgenommen.

Also, 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 benutzen Verschlüsse. Verwechseln Sie den Ausführungspunkt nicht mit Schließungen. Wenn der 'Schnappschuss' der Schließungen im falschen Moment gemacht wird, können die Werte unerwartet sein, aber es wird sicherlich eine Schließung gemacht!



10

Schauen wir uns beide Möglichkeiten an:

(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 ausgeführt wird. Der aktuelle Wert von iwird beibehalten, indem zuerst eine Kopie erstellt wird i2. 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, in dem der aktuelle Wert von ibeibehalten wird i2. Dieser Ansatz verwendet auch die sofortige Ausführung, um den Wert zu erhalten.

Wichtig

Es sollte erwähnt werden, dass die Laufsemantik zwischen beiden Ansätzen NICHT gleich ist. Ihre innere Funktion wird übergeben, setTimeout()während sich seine innere Funktion setTimeout()selbst nennt .

Das Einwickeln beider Codes in einen anderen setTimeout()beweist nicht, dass nur der zweite Ansatz Verschlüsse verwendet. Es gibt einfach nicht dasselbe.

Fazit

Bei beiden Methoden werden Verschlüsse verwendet, daher kommt es auf den persönlichen Geschmack an. Der zweite Ansatz ist einfacher zu "bewegen" oder zu verallgemeinern.


Ich denke, der Unterschied ist: Seine Lösung (1.) erfasst durch Referenz, meine (2.) erfasst durch Wert. In diesem Fall macht es keinen Unterschied, aber wenn ich die Ausführung in ein anderes setTimeout setzen würde, würden wir sehen, dass seine Lösung das Problem hat, dass sie dann den Endwert von i verwendet, nicht den aktuellen, während meine Schwelle verwendet der aktuelle Wert (seitdem vom Wert erfasst).
Leemes

@leemes Sie erfassen beide auf dieselbe Weise. Das Übergeben einer Variablen über ein Funktionsargument oder eine Zuweisung ist dasselbe. Können Sie Ihrer Frage hinzufügen, wie Sie die Ausführung in eine andere verpacken würden setTimeout()?
Ja͢ck

Lassen Sie mich das überprüfen ... Ich wollte zeigen, dass das Funktionsobjekt weitergegeben und die ursprüngliche Variable igeändert werden kann, ohne dass dies Auswirkungen darauf hat, was die Funktion drucken soll, unabhängig davon, wo oder wann wir sie ausführen.
Leemes

Warten Sie, Sie haben keine Funktion an (das äußere) setTimeout übergeben. Entfernen Sie diese ()und übergeben Sie eine Funktion. Sie sehen das 10-fache der Ausgabe 10.
Leemes

@leemes Wie bereits erwähnt, ()ist es genau das, was seinen Code funktioniert, genau wie Ihren (i); Sie haben nicht nur seinen Code verpackt, sondern Änderungen daran vorgenommen. Daher können Sie keinen gültigen Vergleich mehr durchführen.
Ja͢ck

8

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

Ein Abschluss ist eine Funktion, die beim Aufruf den Bereich verwendet, in dem sie deklariert wurde, nicht den Bereich, in dem sie aufgerufen wurde. In JavaScript verhalten sich alle Funktionen so. Variablenwerte in einem Bereich bleiben bestehen, solange eine Funktion vorhanden ist, die noch auf sie verweist. Die Ausnahme von der Regel ist 'this', die 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' 

6

Nach eingehender Prüfung sieht es so aus, als würden Sie beide einen Verschluss verwenden.

Im Fall Ihrer Freunde iwird in der anonymen Funktion 1 und i2in der anonymen Funktion 2 zugegriffen, in der console.logdie vorhanden ist.

In Ihrem Fall greifen Sie auf eine i2anonyme Funktion zu, die console.logvorhanden ist. Fügen Sie debugger;vor console.logund in den Chrome-Entwicklertools unter "Bereichsvariablen" eine Anweisung hinzu, die angibt, unter welchem ​​Bereich die Variable liegt.


2
Der Abschnitt "Schließen" im rechten Bereich wird verwendet, da es keinen genaueren Namen gibt. "Lokal" ist eine stärkere Anzeige als "Schließung".
Rob W


4

Folgendes berücksichtigen. Dadurch wird eine Funktion erstellt und neu erstellt f, die geschlossen wird i, aber andere!:

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

während das Folgende "eine" Funktion "selbst" schließt
(selbst! das Snippet danach verwendet einen einzelnen Referenten f)

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

oder genauer gesagt:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

NB. Die letzte Definition von fis function(){ console.log(9) } before 0 wird gedruckt.

Vorbehalt! Das Verschlusskonzept kann eine zwingende Ablenkung vom Wesen der elementaren Programmierung sein:

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

x-refs.:
Wie funktionieren JavaScript-Schließungen?
Erläuterungen zu Javascript-Verschlüssen
Erfordert ein (JS) -Verschluss eine Funktion innerhalb einer Funktion
? Wie verstehe ich Verschlüsse in Javascript?
Lokale und globale Verwirrung von Javascript-Variablen


Schnipsel zum ersten Mal versucht - nicht sicher, wie man kontrolliert - Run' only was desired - not sure how to remove the Copy`
ekim

-1

Ich möchte mein Beispiel und eine Erklärung zu Schließungen mitteilen. Ich habe ein Python-Beispiel und zwei Abbildungen gemacht, um die Stapelzustände zu demonstrieren.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n * margin_top, a * n, 
            ' ‘ * padding, msg, '  * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)

f('hello')
g(‘good bye!')

Die Ausgabe dieses Codes wäre wie folgt:

*****      hello      #####

      good bye!    ♥♥♥

Hier sind zwei Abbildungen, die Stapel und den am Funktionsobjekt angebrachten Verschluss zeigen.

wenn die Funktion vom Hersteller zurückgegeben wird

wenn die Funktion später aufgerufen wird

Wenn die Funktion über einen Parameter oder eine nichtlokale Variable aufgerufen wird, benötigt der Code lokale Variablenbindungen wie margin_top, padding sowie a, b, n. Um sicherzustellen, dass der Funktionscode funktioniert, sollte auf den Stapelrahmen der Maker-Funktion zugegriffen werden können, der vor langer Zeit entfernt wurde. Dieser wird in dem Abschluss gesichert, den wir zusammen mit dem Funktionsnachrichtenobjekt finden.


Ich möchte diese Antwort entfernen. Mir wurde klar, dass es bei der Frage nicht um die Schließung geht, daher möchte ich sie auf die andere Frage verschieben.
Eunjung Lee

2
Ich glaube, Sie haben die Möglichkeit, Ihre eigenen Inhalte zu löschen. Klicken Sie auf den deleteLink unter der Antwort.
Rory McCrossan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.