Frage Wie funktionieren JavaScript-Verschlüsse?


Wie würden Sie jemandem, der Kenntnisse über die Konzepte, aus denen er besteht (z. B. Funktionen, Variablen und dergleichen), JavaScript-Schließungen erklären, aber die Schließungen selbst nicht verstehen?

ich habe gesehen das Schema Beispiel auf Wikipedia gegeben, aber leider hat es nicht geholfen.


7654


Ursprung


Antworten:


JavaScript-Verschlüsse für Anfänger

Verfasst von Morris am Di, 2006-02-21 10:19. Community bearbeitet seit.

Verschlüsse sind keine Magie

Auf dieser Seite werden die Schließungen erläutert, damit sie von einem Programmierer verstanden werden können - mit funktionierendem JavaScript-Code. Es ist nicht für Gurus oder funktionale Programmierer.

Verschlüsse sind nicht schwer um zu verstehen, sobald das Kernkonzept groked ist. Sie sind jedoch unmöglich zu verstehen, wenn sie wissenschaftliche Arbeiten oder akademisch orientierte Informationen über sie lesen!

Dieser Artikel richtet sich an Programmierer mit Programmierkenntnissen in einer Mainstream-Sprache, die die folgende JavaScript-Funktion lesen können:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Ein Beispiel für eine Schließung

Zwei Ein-Satz-Zusammenfassungen:

  • Eine Schließung ist eine Art zu unterstützen erstklassige Funktionen; Es ist ein Ausdruck, der auf Variablen in seinem Gültigkeitsbereich verweisen kann (wenn er zum ersten Mal deklariert wurde), einer Variablen zugewiesen, als Argument an eine Funktion übergeben oder als Funktionsergebnis zurückgegeben wird.

  • Oder ein Abschluss ist ein Stapelrahmen, der zugewiesen wird, wenn eine Funktion ihre Ausführung beginnt, und nicht befreit nachdem die Funktion zurückkehrt (als wäre ein "Stack-Frame" auf dem Heap und nicht auf dem Stack zugewiesen worden!).

Der folgende Code gibt einen Verweis auf eine Funktion zurück:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

Die meisten JavaScript-Programmierer werden verstehen, wie eine Referenz auf eine Funktion an eine Variable zurückgegeben wird (say2) im obigen Code. Wenn Sie dies nicht tun, müssen Sie sich das ansehen, bevor Sie Schließungen lernen können. Ein Programmierer, der C verwendet, würde sich die Funktion so vorstellen, als würde er einen Zeiger auf eine Funktion und die Variablen zurückgeben say und say2 waren jeweils ein Zeiger auf eine Funktion.

Es gibt einen entscheidenden Unterschied zwischen einem C-Zeiger auf eine Funktion und einer JavaScript-Referenz auf eine Funktion. In JavaScript können Sie sich eine Funktionsreferenzvariable als einen Zeiger auf eine Funktion vorstellen auch als versteckter Zeiger auf eine Schließung.

Der obige Code hat eine Schließung wegen der anonymen Funktion function() { console.log(text); } ist erklärt Innerhalb eine andere Funktion, sayHello2() in diesem Beispiel. In JavaScript, wenn Sie die function Schlüsselwort in einer anderen Funktion, erstellen Sie eine Schließung.

In C und den meisten anderen gängigen Sprachen, nach Eine Funktion gibt zurück, alle lokalen Variablen sind nicht mehr zugänglich, da der Stack-Frame zerstört ist.

Wenn Sie in JavaScript eine Funktion innerhalb einer anderen Funktion deklarieren, können die lokalen Variablen auch nach dem Zurückkehren von der aufgerufenen Funktion zugänglich bleiben. Dies wird oben demonstriert, weil wir die Funktion aufrufen say2()nachdem wir von zurückgekommen sind sayHello2(). Beachten Sie, dass der aufgerufene Code auf die Variable verweist text, das war a lokale Variable der Funktion sayHello2().

function() { console.log(text); } // Output of say2.toString();

Mit Blick auf die Ausgabe von say2.toString()können wir sehen, dass der Code auf die Variable verweist text. Die anonyme Funktion kann verweisen text was den Wert enthält 'Hello Bob' weil die lokalen Variablen von sayHello2() sind in einem Verschluss gehalten.

Die Magie ist, dass eine Funktionsreferenz in JavaScript auch eine geheime Referenz auf die Schließung hat, in der sie erstellt wurde - ähnlich wie Delegaten ein Methodenzeiger und eine geheime Referenz auf ein Objekt sind.

Mehr Beispiele

Aus irgendeinem Grund scheinen Schließungen wirklich schwer zu verstehen, wenn man über sie liest, aber wenn man einige Beispiele sieht, wird klar, wie sie funktionieren (es hat eine Weile gedauert). Ich empfehle, die Beispiele sorgfältig durchzuarbeiten, bis Sie verstehen, wie sie funktionieren. Wenn Sie anfangen, Schließungen zu verwenden, ohne vollständig zu verstehen, wie sie funktionieren, würden Sie bald einige sehr seltsame Fehler erstellen!

Beispiel 3

Dieses Beispiel zeigt, dass die lokalen Variablen nicht kopiert werden - sie werden als Referenz gespeichert. Es ist so, als würde man einen Stack-Frame im Speicher behalten, wenn die äußere Funktion beendet wird!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Beispiel 4

Alle drei globalen Funktionen haben einen gemeinsamen Bezug auf die gleich Schließung, weil sie alle in einem einzigen Aufruf an erklärt werden setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Die drei Funktionen haben Zugriff auf die gleiche Schließung - die lokalen Variablen von setupSomeGlobals() wenn die drei Funktionen definiert wurden.

Beachten Sie, dass im obigen Beispiel, wenn Sie anrufen setupSomeGlobals() wieder, dann wird eine neue Schließung (Stack-Frame!) erstellt. Das alte gLogNumber, gIncreaseNumber, gSetNumber Variablen werden mit überschrieben Neu Funktionen, die den neuen Abschluss haben. (Wenn Sie in JavaScript eine Funktion in einer anderen Funktion deklarieren, werden die Innenfunktion (en) in JavaScript erneut erstellt jeder Zeit die Außenfunktion aufgerufen wird.)

Beispiel 5

Dieses Beispiel zeigt, dass die Closure lokale Variablen enthält, die innerhalb der äußeren Funktion deklariert wurden, bevor sie beendet wurde. Beachten Sie, dass die Variable alice ist eigentlich nach der anonymen Funktion deklariert. Die anonyme Funktion wird zuerst deklariert; und wenn diese Funktion aufgerufen wird, kann auf die zugegriffen werden alice Variable weil alice ist im gleichen Umfang (JavaScript tut variables Heben). Ebenfalls sayAlice()() ruft direkt die Funktionsreferenz auf, von der zurückgegeben wird sayAlice() - Es ist genau das gleiche wie vorher, aber ohne die temporäre Variable.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: Beachten Sie auch, dass die say Die Variable befindet sich ebenfalls in der Closure und kann von jeder anderen Funktion aufgerufen werden, die möglicherweise in der Closure deklariert wird sayAlice(), oder es könnte rekursiv innerhalb der Inside-Funktion zugegriffen werden.

Beispiel 6

Dieser ist eine echte Herausforderung für viele Leute, also müssen Sie es verstehen. Seien Sie sehr vorsichtig, wenn Sie eine Funktion innerhalb einer Schleife definieren: Die lokalen Variablen aus der Schließung handeln möglicherweise nicht so, wie Sie vielleicht zuerst denken.

Sie müssen die Funktion "Variable hissen" in Javascript verstehen, um dieses Beispiel zu verstehen.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

Die Linie result.push( function() {console.log(item + ' ' + list[i])} Fügt dem Ergebnisarray dreimal eine Referenz auf eine anonyme Funktion hinzu. Wenn Sie mit anonymen Funktionen nicht so vertraut sind, denken Sie daran:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Beachten Sie, dass beim Ausführen des Beispiels "item2 undefined" wird dreimal protokolliert! Dies liegt daran, dass es genau wie in den vorherigen Beispielen nur einen Abschluss für die lokalen Variablen gibt buildList (welche sind result, i und item). Wenn die anonymen Funktionen in der Zeile aufgerufen werden fnlist[j](); Sie verwenden alle den gleichen Einzelabschluss und verwenden den aktuellen Wert für i und item innerhalb dieser einen Schließung (wo i hat einen Wert von 3 weil die Schleife abgeschlossen war, und item hat einen Wert von 'item2'). Beachten Sie, dass wir von 0 ausgehend indexieren item hat einen Wert von item2. Und das i ++ wird erhöht i zu dem Wert 3.

Es kann hilfreich sein zu sehen, was passiert, wenn eine Block-Level-Deklaration der Variablen stattfindet item wird benutzt (über die let Schlüsselwort) anstelle einer funktionsorientierten Variablendeklaration über die var Stichwort. Wenn diese Änderung vorgenommen wird, dann jede anonyme Funktion im Array result hat seine eigene Schließung; Wenn das Beispiel ausgeführt wird, lautet die Ausgabe wie folgt:

item0 undefined
item1 undefined
item2 undefined

Wenn die Variable i wird auch mit definiert let Anstatt von var, dann ist die Ausgabe:

item0 1
item1 2
item2 3

Beispiel 7

In diesem letzten Beispiel erstellt jeder Aufruf der Hauptfunktion einen separaten Abschluss.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Zusammenfassung

Wenn alles völlig unklar erscheint, ist es am besten, mit den Beispielen zu spielen. Eine Erklärung zu lesen ist viel schwieriger als Beispiele zu verstehen. Meine Erklärungen zu Verschlüssen und Stapelrahmen usw. sind technisch nicht korrekt - sie sind grobe Vereinfachungen, die zum besseren Verständnis beitragen sollen. Sobald die Grundidee geklärt ist, können Sie die Details später abholen.

Endpunkte:

  • Wann immer Sie verwenden function In einer anderen Funktion wird ein Verschluss verwendet.
  • Wann immer Sie verwenden eval() Innerhalb einer Funktion wird ein Verschluss verwendet. Der Text du eval kann auf lokale Variablen der Funktion verweisen und innerhalb von eval Sie können sogar neue lokale Variablen mithilfe von erstellen eval('var foo = …')
  • Wenn Sie verwenden new Function(…) (das Funktionskonstruktor) Innerhalb einer Funktion erzeugt es keinen Abschluss. (Die neue Funktion kann nicht auf die lokalen Variablen der äußeren Funktion verweisen.)
  • Eine Schließung in JavaScript ist wie eine Kopie aller lokalen Variablen, so wie sie waren, als eine Funktion beendet wurde.
  • Es ist wahrscheinlich am besten zu glauben, dass eine Schließung immer nur ein Eintrag für eine Funktion ist und die lokalen Variablen zu dieser Schließung hinzugefügt werden.
  • Eine neue Menge lokaler Variablen wird jedes Mal beibehalten, wenn eine Funktion mit einer Closure aufgerufen wird (vorausgesetzt, die Funktion enthält eine Funktionsdeklaration innerhalb dieser Funktion und eine Referenz auf diese interne Funktion wird entweder zurückgegeben oder eine externe Referenz wird in irgendeiner Weise beibehalten ).
  • Zwei Funktionen sehen möglicherweise so aus, als hätten sie denselben Quelltext, aber aufgrund ihrer "versteckten" Schließung ein völlig anderes Verhalten. Ich glaube nicht, dass JavaScript-Code tatsächlich herausfinden kann, ob eine Funktionsreferenz eine Schließung hat oder nicht.
  • Wenn Sie versuchen, dynamische Quellcodeänderungen vorzunehmen (zum Beispiel: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), es wird nicht funktionieren, wenn myFunction ist eine Schließung (natürlich würden Sie niemals daran denken, die Quelltext-String-Substitution zur Laufzeit durchzuführen, aber ...).
  • Es ist möglich, Funktionsdeklarationen innerhalb von Funktionsdeklarationen innerhalb von Funktionen zu erhalten - und Sie können Schließungen auf mehr als einer Ebene erhalten.
  • Ich denke normalerweise ist eine Schließung ein Begriff für die Funktion zusammen mit den Variablen, die erfasst werden. Beachten Sie, dass ich diese Definition in diesem Artikel nicht verwende!
  • Ich vermute, dass sich JavaScript-Verschlüsse von denen unterscheiden, die normalerweise in funktionalen Sprachen zu finden sind.

Links

Vielen Dank

Wenn Sie haben gerade lernte Schließungen (hier oder anderswo!), dann bin ich an jedem Feedback von Ihnen interessiert, wenn Sie Änderungen vorschlagen, die diesen Artikel verständlicher machen könnten. Senden Sie eine E-Mail an morrisjohns.com (morris_closure @). Bitte beachten Sie, dass ich kein Guru für JavaScript bin - auch nicht für Schließungen.


Original Post von Morris kann in der gefunden werden Internetarchiv.


6160



Wenn Sie das Funktionsschlüsselwort in einer anderen Funktion sehen, hat die innere Funktion Zugriff auf Variablen in der äußeren Funktion.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Dies wird immer 16 protokollieren, weil bar kann auf die zugreifen x was als ein Argument zu definiert wurde foound es kann auch zugreifen tmp von foo.

Das ist ein Verschluss. Eine Funktion muss nicht Rückkehr um eine Schließung zu nennen. Wenn Sie einfach auf Variablen außerhalb Ihres unmittelbaren lexikalischen Geltungsbereichs zugreifen, entsteht eine Schließung.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

Die obige Funktion protokolliert 16, weil bar kann sich immer noch darauf beziehen x und tmp, obwohl es nicht mehr direkt im Bereich liegt.

Jedoch seit tmp hängt immer noch drinnen herum barSchließung, es wird auch inkrementiert. Es wird bei jedem Anruf inkrementiert bar.

Das einfachste Beispiel für eine Schließung ist dies:

var a = 10;
function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Wenn eine JavaScript-Funktion aufgerufen wird, wird ein neuer Ausführungskontext erstellt. Zusammen mit den Funktionsargumenten und dem Elternobjekt erhält dieser Ausführungskontext auch alle außerhalb davon deklarierten Variablen (im obigen Beispiel sowohl 'a' als auch 'b').

Es ist möglich, mehr als eine Abschlussfunktion zu erstellen, indem Sie entweder eine Liste von ihnen zurückgeben oder sie auf globale Variablen setzen. Alle diese beziehen sich auf die gleich  x und das gleiche tmp, sie machen keine eigenen Kopien.

Hier die Nummer x ist eine wörtliche Zahl. Wie bei anderen Literalen in JavaScript, wann foo heißt, die Nummer x ist kopiert in foo als sein Argument x.

Auf der anderen Seite verwendet JavaScript immer Referenzen, wenn es um Objekte geht. Wenn du sagst, du hast angerufen foomit einem Objekt wird die Schließung zurückgegeben Referenz das ursprüngliche Objekt!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Wie erwartet, ruft jeder Anruf an bar(10) wird erhöht x.memb. Was nicht erwartet werden könnte, ist das x bezieht sich einfach auf das gleiche Objekt wie die age Variable! Nach ein paar Anrufen an bar, age.memb wird 2! Diese Referenzierung ist die Grundlage für Speicherverluste bei HTML-Objekten.


3804



VORWORT: Diese Antwort wurde geschrieben, als die Frage war:

Wie der alte Albert sagte: "Wenn du es einem Sechsjährigen nicht erklären kannst, dann verstehst du es wirklich nicht selbst." Nun, ich versuchte einem 27-jährigen Freund JS-Schließungen zu erklären und scheiterte komplett.

Kann jemand denken, dass ich 6 und seltsam interessiert an diesem Thema bin?

Ich bin mir ziemlich sicher, dass ich einer der wenigen war, die versuchten, die anfängliche Frage wörtlich zu nehmen. Seitdem hat sich die Frage mehrmals verändert, so dass meine Antwort jetzt unglaublich dumm und fehl am Platz erscheint. Hoffentlich bleibt die allgemeine Idee der Geschichte für einige lustig.


Ich bin ein großer Fan von Analogie und Metapher, wenn ich schwierige Konzepte erkläre, also lass mich meine Hand mit einer Geschichte versuchen.

Es war einmal:

Da war eine Prinzessin ...

function princess() {

Sie lebte in einer wundervollen Welt voller Abenteuer. Sie traf ihren Märchenprinzen, ritt auf einem Einhorn um ihre Welt, kämpfte gegen Drachen, begegnete sprechenden Tieren und vielen anderen phantastischen Dingen.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Aber sie musste immer wieder in ihre langweilige Welt von Hausarbeiten und Erwachsenen zurückkehren.

    return {

Und sie erzählte ihnen oft von ihrem neuesten Abenteuer als Prinzessin.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Aber alles, was sie sehen würden, ist ein kleines Mädchen ...

var littleGirl = princess();

... Geschichten über Magie und Fantasie erzählen.

littleGirl.story();

Und obwohl die Erwachsenen von echten Prinzessinnen wussten, würden sie niemals an die Einhörner oder Drachen glauben, weil sie sie nie sehen konnten. Die Erwachsenen sagten, dass sie nur innerhalb der Vorstellungskraft des kleinen Mädchens existierten.

Aber wir kennen die wahre Wahrheit; dass das kleine Mädchen mit der Prinzessin drinnen ...

... ist wirklich eine Prinzessin mit einem kleinen Mädchen drin.


2251



Wenn wir die Frage ernst nehmen, sollten wir herausfinden, was ein typischer 6-Jähriger kognitiv kann, obwohl zugegebenermaßen jemand, der sich für JavaScript interessiert, nicht so typisch ist.

Auf Entwicklung der Kindheit: 5 bis 7 Jahre  es sagt:

Ihr Kind wird in der Lage sein, zwei Schritte zu folgen. Wenn Sie zum Beispiel zu Ihrem Kind sagen: "Geh in die Küche und hol mir einen Müllsack", werden sie sich an diese Richtung erinnern können.

Anhand dieses Beispiels können wir die Schließungen wie folgt erläutern:

Die Küche ist eine Schließung, die eine lokale Variable hat, genannt trashBags. Es gibt eine Funktion in der Küche genannt getTrashBag das bekommt einen Müllsack und gibt es zurück.

Wir können das in JavaScript wie folgt codieren:

function makeKitchen () {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

kitchen.getTrashBag(); // returns trash bag C
kitchen.getTrashBag(); // returns trash bag B
kitchen.getTrashBag(); // returns trash bag A

Weitere Punkte, die erklären, warum Schließungen interessant sind:

  • Jedes Mal makeKitchen() heißt, es wird eine neue Schließung mit eigenen eigenen erstellt trashBags.
  • Das trashBags Variable ist lokal für das Innere jeder Küche und ist nicht zugänglich außerhalb, aber die innere Funktion auf der getTrashBagEigentum hat Zugang zu ihm.
  • Jeder Funktionsaufruf erzeugt einen Abschluss, aber es wäre nicht notwendig, den Abschluss beizubehalten, es sei denn, eine innere Funktion, die Zugriff auf das Innere des Abschlusses hat, kann von außerhalb des Abschlusses aufgerufen werden. Rückgabe des Objekts mit der getTrashBag Funktion macht das hier.

691



Der Strohmann

Ich muss wissen, wie oft eine Schaltfläche geklickt wurde, und bei jedem dritten Klick etwas tun ...

Ziemlich offensichtliche Lösung

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Jetzt wird das funktionieren, aber es greift in den äußeren Bereich ein, indem es eine Variable hinzufügt, deren einziger Zweck es ist, die Zählung zu verfolgen. In einigen Situationen wäre dies vorzuziehen, da Ihre äußere Anwendung möglicherweise Zugriff auf diese Informationen benötigt. Aber in diesem Fall ändern wir nur das Verhalten jedes dritten Klicks, also ist es besser schließe diese Funktionalität in den Event-Handler ein.

Betrachten Sie diese Option

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Beachten Sie ein paar Dinge hier.

Im obigen Beispiel verwende ich das Schließverhalten von JavaScript. Mit diesem Verhalten kann jede Funktion unbegrenzt auf den Bereich zugreifen, in dem sie erstellt wurde. Um dies praktisch anzuwenden, rufe ich sofort eine Funktion auf, die eine andere Funktion zurückgibt, und da die Funktion, die ich zurückgebe, Zugriff auf die interne Zählvariable hat (aufgrund des oben erläuterten Schließverhaltens), ergibt sich daraus ein privater Bereich für die Verwendung Funktion ... Nicht so einfach? Lass es uns verdünnen ...

Ein einfacher Ein-Zeilen-Verschluss

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Alle Variablen außerhalb der zurückgegebenen Funktion sind für die zurückgegebene Funktion verfügbar, aber für das zurückgegebene Funktionsobjekt sind sie nicht direkt verfügbar ...

func();  // Alerts "val"
func.a;  // Undefined

Hole es? In unserem Beispiel ist die count-Variable also in der Closure enthalten und immer für den Event-Handler verfügbar. Daher behält sie ihren Zustand von Klick zu Klick bei.

Auch dieser private Variablenstatus ist völlig zugänglich, sowohl für Lesungen als auch für die Zuordnung zu seinen privaten Bereichsvariablen.

Da gehst du hin; Jetzt kapseln Sie dieses Verhalten vollständig ein.

Vollständiger Blogbeitrag (einschließlich jQuery-Überlegungen)


526



Schließungen sind schwer zu erklären, weil sie verwendet werden, um Verhaltensweisen zum Funktionieren zu bringen, von denen jeder intuitiv erwartet, dass sie trotzdem funktionieren. Ich finde den besten Weg, sie zu erklären (und die Art und Weise, wie ich gelernt, was sie tun) ist, sich die Situation ohne sie vorzustellen:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Was würde hier passieren, wenn JavaScript nicht Schließungen kennen? Ersetzen Sie einfach den Aufruf in der letzten Zeile durch seinen Methodenkörper (was im Grunde das ist, was Funktionsaufrufe tun) und Sie erhalten:

console.log(x + 3);

Nun, wo ist die Definition von x? Wir haben es im aktuellen Umfang nicht definiert. Die einzige Lösung ist es zu lassen plus5  tragen sein Umfang (oder eher, der Umfang seiner Eltern) herum. Diesen Weg, x ist klar definiert und an den Wert 5 gebunden.


444



Dies ist ein Versuch, mehrere (mögliche) Missverständnisse über Schließungen zu beseitigen, die in einigen der anderen Antworten auftauchen.

  • Eine Schließung wird nicht nur erzeugt, wenn Sie eine innere Funktion zurückgeben. In der Tat, die umschließende Funktion muss überhaupt nicht zurückkehren um seine Schließung zu schaffen. Sie können stattdessen Ihre innere Funktion einer Variablen in einem äußeren Bereich zuweisen oder sie als Argument an eine andere Funktion übergeben, wo sie sofort oder zu einem späteren Zeitpunkt aufgerufen werden kann. Daher wird wahrscheinlich die Schließung der umschließenden Funktion erstellt sobald die umschließende Funktion aufgerufen wird da jede innere Funktion Zugriff auf diesen Abschluss hat, wann immer die innere Funktion aufgerufen wird, bevor oder nachdem die umschließende Funktion zurückkehrt.
  • Eine Schließung verweist nicht auf eine Kopie der alte Werte von Variablen in ihrem Umfang. Die Variablen selbst sind Teil des Abschlusses, und daher ist der Wert, der beim Zugriff auf eine dieser Variablen zu sehen ist, der letzte Wert zum Zeitpunkt des Zugriffs. Aus diesem Grund können innere Funktionen, die innerhalb von Schleifen erstellt werden, schwierig sein, da jeder Zugriff auf die gleichen äußeren Variablen hat, anstatt eine Kopie der Variablen zum Zeitpunkt der Erstellung oder des Aufrufs der Funktion zu erfassen.
  • Die "Variablen" in einem Abschluss enthalten alle genannten Funktionen innerhalb der Funktion deklariert. Sie enthalten auch Argumente der Funktion. Eine Sperrung hat auch Zugriff auf die Variablen des enthaltenden Abschlusses bis hin zum globalen Gültigkeitsbereich.
  • Closures verwenden Speicher, sie verursachen jedoch keine Speicherlecks da JavaScript selbst seine eigenen kreisförmigen Strukturen aufräumt, die nicht referenziert sind. Internet Explorer-Speicherlecks, die Closures enthalten, werden erstellt, wenn DOM-Attributwerte, die auf Closures verweisen, nicht getrennt werden können. Dadurch werden Verweise auf möglicherweise kreisförmige Strukturen beibehalten.

342



OK, 6-jährige Schließungen Fan. Möchten Sie das einfachste Beispiel für eine Schließung hören?

Stellen wir uns die nächste Situation vor: Ein Fahrer sitzt in einem Auto. Dieses Auto ist in einem Flugzeug. Flugzeug ist im Flughafen. Die Fähigkeit des Fahrers, auf Dinge außerhalb seines Autos zuzugreifen, aber innerhalb des Flugzeugs, selbst wenn das Flugzeug einen Flughafen verlässt, ist eine Schließung. Das ist es. Wenn Sie 27 Jahre alt werden, schauen Sie sich die ausführlichere Erklärung oder im Beispiel unten.

Hier ist, wie ich meine Flugzeuggeschichte in den Code umwandeln kann.

var plane = function (defaultAirport) {

    var lastAirportLeft = defaultAirport;

    var car = {
        driver: {
            startAccessPlaneInfo: function () {
                setInterval(function () {
                    console.log("Last airport was " + lastAirportLeft);
                }, 2000);
            }
        }
    };
    car.driver.startAccessPlaneInfo();

    return {
        leaveTheAirport: function (airPortName) {
            lastAirportLeft = airPortName;
        }
    }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

330



EIN Schließung ist ähnlich wie ein Objekt. Es wird instanziiert, wenn Sie eine Funktion aufrufen.

Der Umfang eines Schließung in JavaScript ist lexikalisch, was bedeutet, dass alles, was in der Funktion enthalten ist Schließung gehört zu, hat Zugriff auf alle Variablen, die darin enthalten sind.

Eine Variable ist in der. Enthalten Schließung wenn du

  1. weise es zu var foo=1; oder
  2. einfach schreiben var foo;

Wenn eine innere Funktion (eine in einer anderen Funktion enthaltene Funktion) auf eine solche Variable zugreift, ohne sie in ihrem eigenen Geltungsbereich mit var zu definieren, ändert sie den Inhalt der Variablen im äußeren Schließung.

EIN Schließung überlebt die Laufzeit der Funktion, die es hervorgebracht hat. Wenn andere Funktionen es aus dem machen Schließung / Umfangin denen sie definiert sind (zum Beispiel als Rückgabewerte), werden diese weiterhin darauf verweisen Schließung.

Beispiel

 function example(closure) {
   // define somevariable to live in the closure of example
   var somevariable = 'unchanged';

   return {
     change_to: function(value) {
       somevariable = value;
     },
     log: function(value) {
       console.log('somevariable of closure %s is: %s',
         closure, somevariable);
     }
   }
 }

 closure_one = example('one');
 closure_two = example('two');

 closure_one.log();
 closure_two.log();
 closure_one.change_to('some new value');
 closure_one.log();
 closure_two.log();

Ausgabe

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

319