function stackoverflow - Wie funktionieren JavaScript-Verschlüsse?



http (25)

Wie würden Sie JavaScript-Verschlüsse jemandem erklären, der die Konzepte kennt, aus denen er besteht (z. B. Funktionen, Variablen und dergleichen), die Verschlüsse jedoch selbst nicht verstehen?

Ich habe das Beispielschema von Wikipedia gesehen, aber es hat leider nicht geholfen.


Answers

Als Vater eines sechsjährigen Kindes, das gerade junge Kinder unterrichtet (und ein relativ unerfahrener Kodierer ohne formale Schulung, so dass Korrekturen erforderlich sind), denke ich, dass die Lektion am besten durch praxisorientiertes Spielen erhalten bleibt. Wenn der Sechsjährige bereit ist zu verstehen, was eine Schließung ist, dann sind sie alt genug, um selbst einen Versuch zu unternehmen. Ich würde vorschlagen, den Code in jsfiddle.net einzufügen, ein wenig zu erklären und sie in Ruhe lassen, um einen einzigartigen Song zusammenzustellen. Der nachstehende erläuternde Text ist wahrscheinlich für einen 10-jährigen geeignet.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

ANLEITUNG

DATEN: Daten sind eine Sammlung von Fakten. Es können Zahlen, Wörter, Maße, Beobachtungen oder auch nur Beschreibungen von Dingen sein. Man kann es nicht anfassen, riechen oder schmecken. Sie können es aufschreiben, sprechen und hören. Sie können es verwenden, um mit einem Computer Berührungsgeruch und Geschmack zu erzeugen . Es kann von einem Computer mit Code nützlich gemacht werden.

CODE: Alle oben genannten Texte werden als Code bezeichnet . Es ist in JavaScript geschrieben.

JAVASCRIPT: JavaScript ist eine Sprache. Wie Englisch oder Französisch oder Chinesisch sind Sprachen. Es gibt viele Sprachen, die von Computern und anderen elektronischen Prozessoren verstanden werden. Damit JavaScript von einem Computer verstanden werden kann, ist ein Dolmetscher erforderlich. Stellen Sie sich vor, ein Lehrer, der nur Russisch spricht, kommt, um Ihre Klasse in der Schule zu unterrichten. Wenn der Lehrer "все садятся" sagt, würde die Klasse nicht verstehen. Aber zum Glück haben Sie einen russischen Schüler in Ihrer Klasse, der jedem sagt, dies bedeutet "alle setzen sich" - also tun Sie es alle. Die Klasse ist wie ein Computer und der russische Schüler ist der Dolmetscher. Bei JavaScript wird der gebräuchlichste Interpreter als Browser bezeichnet.

BROWSER: Wenn Sie auf einem Computer, Tablet oder Telefon eine Internetverbindung herstellen, um eine Website zu besuchen, verwenden Sie einen Browser. Beispiele, die Sie kennen, sind Internet Explorer, Chrome, Firefox und Safari. Der Browser kann JavaScript verstehen und dem Computer mitteilen, was er tun muss. Die JavaScript-Anweisungen werden als Funktionen bezeichnet.

FUNKTION: Eine Funktion in JavaScript ist wie eine Factory. Es könnte eine kleine Fabrik mit nur einer Maschine sein. Oder es enthält viele andere kleine Fabriken, in denen viele Maschinen unterschiedliche Aufgaben ausführen. In einer realen Bekleidungsfabrik könnten Sie Unmengen von Stoff und Garnspulen haben, die hineingehen und T-Shirts und Jeans herauskommen. Unsere JavaScript-Fabrik verarbeitet nur Daten, sie kann nicht nähen, ein Loch bohren oder Metall schmelzen. In unserer JavaScript-Factory gehen Daten ein und Daten werden ausgegeben.

Das ganze Datenmaterial klingt etwas langweilig, aber es ist wirklich sehr cool. Wir könnten eine Funktion haben, die einem Roboter sagt, was er zum Abendessen machen soll. Nehmen wir an, ich lade Sie und Ihren Freund in mein Haus ein. Am liebsten magst du Hähnchenschenkel, ich mag Würstchen, dein Freund will immer was du willst und mein Freund isst kein Fleisch.

Ich habe keine Zeit zum Einkaufen, daher muss die Funktion wissen, was wir im Kühlschrank haben, um Entscheidungen treffen zu können. Jede Zutat hat eine andere Garzeit und wir möchten, dass alles vom Roboter gleichzeitig heiß serviert wird. Wir müssen die Funktion mit den Daten darüber versehen, was uns gefällt, die Funktion könnte mit dem Kühlschrank "sprechen" und die Funktion könnte den Roboter steuern.

Eine Funktion hat normalerweise einen Namen, Klammern und geschweifte Klammern. So was:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Beachten Sie dies /*...*/und //stoppen Sie, dass der Code vom Browser gelesen wird.

NAME: Sie können eine Funktion zu jedem beliebigen Wort aufrufen. Das Beispiel "cookMeal" ist typisch, wenn zwei Wörter miteinander verbunden werden und das zweite am Anfang einen Großbuchstaben hat - dies ist jedoch nicht erforderlich. Es kann kein Leerzeichen enthalten und es kann keine eigene Zahl sein.

PARENTHESES: "Klammern" oder ()sind der Briefkasten an der Tür der JavaScript-Funktionsfabrik oder ein Briefkasten auf der Straße zum Senden von Informationspaketen an die Fabrik. Manchmal kann die Postbox markiert werden zum Beispiel cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , in dem Fall , dass Sie wissen , welche Daten Sie es geben.

BRACES: "Zahnspangen", die so aussehen, {}sind die getönten Fenster unserer Fabrik. Von der Fabrik aus kann man nach außen blicken, aber von außen sieht man nicht hinein.

DAS LANGE CODE-BEISPIEL OBEN

Unser Code beginnt mit dem Wort - Funktion , so dass wir wissen , dass es eine ist! Dann der Name der Funktion sing - das ist meine eigene Beschreibung der Funktion. Dann Klammern () . Die Klammern sind immer für eine Funktion vorhanden. Manchmal sind sie leer, und manchmal haben sie etwas in Dieses hat ein Wort in.: (person). Danach gibt es eine Klammer wie diese {. Dies markiert den Beginn der Funktion sing () . Es hat einen Partner, der das Ende von sing () so markiert}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Diese Funktion kann also etwas mit dem Singen zu tun haben und einige Daten über eine Person benötigen. Es enthält Anweisungen, um etwas mit diesen Daten zu tun.

Nach der Funktion sing () befindet sich jetzt am Ende des Codes die Zeile

var person="an old lady";

VARIABLE: Die Buchstaben var stehen für "variable". Eine Variable ist wie ein Umschlag. Dieser Umschlag ist außen mit "Person" gekennzeichnet. Auf der Innenseite befindet sich ein Zettel mit den Informationen, die unsere Funktion benötigt, einige Buchstaben und Leerzeichen, die wie ein String miteinander verbunden sind (es wird ein String genannt), aus dem der Ausdruck "eine alte Dame" besteht. Unser Umschlag könnte andere Arten von Dingen enthalten, wie Zahlen (als Ganzzahlen bezeichnet), Anweisungen (als Funktionen bezeichnet), Listen (als Arrays bezeichnet ). Da diese Variable außerhalb aller geschweiften Klammern {}steht und Sie durch die getönten Fenster sehen können, wenn Sie sich in den geschweiften Klammern befinden, kann diese Variable von überall im Code gesehen werden. Wir nennen dies eine "globale Variable".

GLOBALE VARIABLE: person ist eine globale Variable. Das heißt, wenn Sie ihren Wert von "alte Dame" in "jungen Mann" ändern , bleibt die Person ein junger Mann, bis Sie sich entscheiden, sie erneut zu ändern und eine andere Funktion auszuführen Der Code kann sehen, dass es ein junger Mann ist. Klicken Sie auf die F12Schaltfläche, oder sehen Sie sich die Option Optionen an, um die Entwicklerkonsole eines Browsers zu öffnen, und geben Sie "Person" ein, um den Wert anzuzeigen. Tippen Sie person="a young man", um es zu ändern, und geben Sie dann erneut "Person" ein, um zu sehen, dass es geändert wurde.

Danach haben wir die Leitung

sing(person);

Diese Zeile ruft die Funktion auf, als würde sie einen Hund aufrufen

"Komm singen , komm und hol dir Person !"

Wenn der Browser den JavaScript-Code in diese Zeile geladen hat, startet er die Funktion. Ich füge die Zeile am Ende hinzu, um sicherzustellen, dass der Browser über alle Informationen verfügt, die zum Ausführen erforderlich sind.

Funktionen definieren Aktionen - die Hauptfunktion ist das Singen. Es enthält eine Variable namens firstPart, die für den Gesang der Person gilt, die für jeden der Verse des Songs gilt: "Es gab" + Person + ", die geschluckt haben". Wenn Sie firstPart in die Konsole eingeben , erhalten Sie keine Antwort, da die Variable in einer Funktion eingeschlossen ist - der Browser kann nicht in die getönten Fenster der geschweiften Klammern sehen.

VERSCHLÜSSE: Die Verschlüsse sind die kleineren Funktionen, die sich in der großen sing () - Funktion befinden. Die kleinen Fabriken in der großen Fabrik. Sie haben jeweils ihre eigenen geschweiften Klammern, was bedeutet, dass die Variablen in ihnen von außen nicht sichtbar sind. Deshalb können die Namen der Variablen ( Kreatur und Ergebnis ) in den Schließungen wiederholt werden, jedoch mit unterschiedlichen Werten. Wenn Sie diese Variablennamen in das Konsolenfenster eingeben, wird der Wert nicht angezeigt, da er durch zwei Ebenen getönter Fenster verborgen wird.

Die Schließungen wissen alle, was die Variable firstPart der Funktion sing () ist, weil sie aus ihren getönten Fenstern sehen können.

Nach den Schließungen kommen die Linien

fly();
spider();
bird();
cat();

Die sing () - Funktion ruft jede dieser Funktionen in der angegebenen Reihenfolge auf. Dann wird die Arbeit der sing () - Funktion erledigt.


Der Strohmann

Ich muss wissen, wie oft eine Schaltfläche angeklickt 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 dies funktionieren, aber es greift in den äußeren Bereich ein, indem eine Variable hinzugefügt wird, deren einziger Zweck darin besteht, die Zählung zu verfolgen. In einigen Situationen ist dies vorzuziehen, da Ihre äußere Anwendung möglicherweise auf diese Informationen zugreifen muss. In diesem Fall ändern wir jedoch nur das Verhalten jedes dritten Klicks. Es ist daher empfehlenswert, diese Funktionalität in den Event-Handler aufzunehmen .

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 hier ein paar Dinge.

Im obigen Beispiel verwende ich das Schließverhalten von JavaScript. Durch dieses 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. Da die zurückgegebene Funktion Zugriff auf die interne count-Variable hat (aufgrund des oben erläuterten Schließungsverhaltens), führt dies zu einem privaten Anwendungsbereich für das Ergebnis Funktion ... Nicht so einfach? Lassen Sie uns es verdünnen ...

Ein einfacher einzeiliger 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, jedoch nicht direkt für das zurückgegebene Funktionsobjekt.

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

Kapiert? In unserem Hauptbeispiel ist die count-Variable also in der Schließung enthalten und steht dem Event-Handler immer zur Verfügung, sodass sie ihren Status von Klick zu Klick beibehält.

Auf diesen Status der privaten Variablen kann auch vollständig zugegriffen werden, und zwar sowohl zum Lesen als auch zum Zuordnen zu seinen Variablen mit privatem Bereich.

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

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


Eine Funktion in JavaScript ist nicht nur ein Verweis auf eine Reihe von Anweisungen (wie in der Sprache C), sondern enthält auch eine verborgene Datenstruktur, die aus Verweisen auf alle nichtlokalen Variablen besteht, die sie verwendet (erfasste Variablen). Solche zweiteiligen Funktionen werden Schließungen genannt. Jede Funktion in JavaScript kann als Schließung betrachtet werden.

Verschlüsse sind Funktionen mit einem Zustand. "This" ist etwas ähnlich in dem Sinne, dass "this" auch Status für eine Funktion bereitstellt, aber Funktion und "this" sind separate Objekte ("this" ist nur ein ausgefallener Parameter und die einzige Möglichkeit, es dauerhaft an ein Objekt zu binden Funktion ist, einen Abschluss zu erstellen). Während "dieses" und die Funktion immer getrennt voneinander leben, kann eine Funktion nicht von ihrer Schließung getrennt werden, und die Sprache bietet keine Möglichkeit, auf erfasste Variablen zuzugreifen.

Da alle diese externen Variablen, auf die von einer lexikalisch verschachtelten Funktion verwiesen wird, tatsächlich lokale Variablen in der Kette ihrer lexikalisch einschließenden Funktionen sind (globale Variablen können lokale Variablen einer Wurzelfunktion sein), und bei jeder einzelnen Ausführung einer Funktion werden neue Instanzen von erstellt Bei den lokalen Variablen folgt, dass bei jeder Ausführung einer Funktion, die eine verschachtelte Funktion zurückgibt (oder sie anderweitig überträgt, z. B. als Callback registriert), eine neue Schließung (mit ihrem eigenen, möglicherweise eindeutigen Satz von referenzierten nichtlokalen Variablen, die ihre Ausführung darstellen) erstellt wird Kontext).

Es muss auch verstanden werden, dass lokale Variablen in JavaScript nicht auf dem Stapelrahmen erstellt werden, sondern auf dem Heap und nur dann zerstört werden, wenn niemand auf sie verweist. Wenn eine Funktion zurückgegeben wird, werden Verweise auf ihre lokalen Variablen dekrementiert. Sie können jedoch immer noch nicht null sein, wenn sie während der aktuellen Ausführung Teil eines Abschlusses wurden und immer noch von ihren lexikalisch verschachtelten Funktionen referenziert werden (was nur möglich ist, wenn auf die Verweise Bezug genommen wird Diese verschachtelten Funktionen wurden zurückgegeben oder anderweitig in einen externen Code übertragen.

Ein Beispiel:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

Vielleicht etwas mehr als alles andere als der frühreifste Sechsjährige, aber einige Beispiele, die dazu beigetragen haben, dass das Konzept der Schließung in JavaScript für mich klickbar ist.

Ein Abschluss ist eine Funktion, die Zugriff auf den Gültigkeitsbereich einer anderen Funktion (ihre Variablen und Funktionen) hat. Die einfachste Möglichkeit zum Erstellen eines Abschlusses besteht in einer Funktion innerhalb einer Funktion. Der Grund ist, dass eine Funktion in JavaScript immer auf den Gültigkeitsbereich der enthaltenen Funktion zugreifen kann.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ACHTUNG: Affe

Im obigen Beispiel wird outerFunction aufgerufen, die wiederum innerFunction aufruft. Beachten Sie, wie outerVar für innerFunction verfügbar ist. Dies wird durch die korrekte Warnung des Wertes von outerVar bestätigt.

Betrachten Sie nun Folgendes:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ACHTUNG: Affe

referenceToInnerFunction wird auf outerFunction () gesetzt, wodurch einfach eine Referenz auf innerFunction zurückgegeben wird. Wenn referenceToInnerFunction aufgerufen wird, wird outerVar zurückgegeben. Wie oben demonstriert dies erneut, dass innerFunction Zugriff auf outerVar hat, eine Variable von outerFunction. Es ist außerdem interessant zu wissen, dass dieser Zugriff auch nach der Ausführung von outerFunction erhalten bleibt.

Und hier werden die Dinge wirklich interessant. Wenn Sie outerFunction loswerden würden, beispielsweise auf null setzen, könnten Sie denken, dass referenceToInnerFunction seinen Zugriff auf den Wert von outerVar verlieren würde. Dies ist jedoch nicht der Fall.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALARM: Affe ALARM: Affe

Aber wie ist das so? Wie kann referenceToInnerFunction den Wert von outerVar immer noch kennen, nachdem outerFunction auf null gesetzt wurde?

Der Grund, dass referenceToInnerFunction weiterhin auf den Wert von outerVar zugreifen kann, liegt darin, dass innerFunction beim Erstellen des Abschlusses durch das Einfügen von innerFunction in outerFunction einen Verweis auf den Gültigkeitsbereich von outerFunction (seine Variablen und Funktionen) in seine Gültigkeitsbereichskette eingefügt hat. Dies bedeutet, dass innerFunction einen Zeiger oder eine Referenz auf alle Variablen von outerFunction enthält, einschließlich outerVar. Selbst wenn die Ausführung von outerFunction beendet ist oder selbst wenn sie gelöscht oder auf null gesetzt wird, bleiben die Variablen in ihrem Gültigkeitsbereich, wie outerVar, im Speicher, da sie von dem Teil der innerFunction, auf den sie zurückgegeben wurden, auf sie hinweisen referenceToInnerFunction. Um die Variablen von outerVar und dem Rest von outerFunction wirklich aus dem Speicher freizugeben, müssen Sie diesen hervorragenden Verweis auf sie entfernen.B. durch Setzen von referenceToInnerFunction auf null.

//////////

Zwei andere Dinge über Verschlüsse zu beachten. Erstens hat der Abschluss immer Zugriff auf die letzten Werte der enthaltenen Funktion.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

WARNUNG: Gorilla

Zweitens: Wenn ein Abschluss erstellt wird, behält er einen Verweis auf alle Variablen und Funktionen seiner umgebenden Funktion. es kann nicht auswählen und wählen. Verschlüsse sollten jedoch sparsam oder zumindest vorsichtig verwendet werden, da sie speicherintensiv sein können. Viele Variablen können lange nach dem Ausführen einer einschließenden Funktion im Speicher gehalten werden.


Eine Antwort für einen Sechsjährigen (vorausgesetzt er weiß, was eine Funktion ist und was eine Variable ist und welche Daten):

Funktionen können Daten zurückgeben. Eine Art von Daten, die Sie von einer Funktion zurückgeben können, ist eine andere Funktion. Wenn diese neue Funktion zurückgegeben wird, werden alle in der erstellten Funktion verwendeten Variablen und Argumente nicht gelöscht. Stattdessen "schließt die übergeordnete Funktion". Mit anderen Worten, nichts kann hineinschauen und die verwendeten Variablen sehen, außer der zurückgegebenen Funktion. Diese neue Funktion hat eine spezielle Fähigkeit, in die Funktion, die sie erstellt hat, zurückzublicken und die darin enthaltenen Daten zu sehen.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Eine weitere sehr einfache Erklärung ist der Umfang:

Jedes Mal, wenn Sie einen kleineren Bereich innerhalb eines größeren Bereichs erstellen, kann der kleinere Bereich immer sehen, was sich im größeren Bereich befindet.


Ich habe vor einiger Zeit einen Blogbeitrag geschrieben, der die Schließungen erklärt. Hier ist , was ich sagte , über Schließungen in Bezug auf , warum Sie ein wünschen würde.

Schließungen sind eine Möglichkeit, eine Funktion über persistente, private Variablen zu verfügen , d. H. Variablen , von denen nur eine Funktion weiß, wo sie Informationen aus früheren Zeiten nachverfolgen können.

In diesem Sinne lassen sie eine Funktion wie ein Objekt mit privaten Attributen wirken.

Vollständiger Beitrag:

Also, was sind diese Verschlusssachen?


Beispiel für den ersten Punkt von dlaliberte:

Ein Abschluss wird nicht nur erstellt, wenn Sie eine innere Funktion zurückgeben. In der Tat muss die einschließende Funktion überhaupt nicht zurückkehren. Sie können stattdessen Ihre innere Funktion einer Variablen in einem äußeren Gültigkeitsbereich zuweisen oder sie als Argument an eine andere Funktion übergeben, wo sie sofort verwendet werden kann. Daher besteht die Schließung der einschließenden Funktion wahrscheinlich bereits zu dem Zeitpunkt, zu dem die einschließende Funktion aufgerufen wurde, da jede innere Funktion auf sie zugreifen kann, sobald sie aufgerufen wird.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

Ich würde sie einfach auf die Mozilla Closures-Seite verweisen . Es ist die beste, prägnanteste und einfachste Erklärung der Grundlagen und praktischen Anwendung, die ich gefunden habe. Es wird dringend jedem empfohlen, der JavaScript lernt.

Und ja, ich würde es sogar einem Sechsjährigen empfehlen - wenn der Sechsjährige von Schließungen erfährt, ist es logisch, dass sie bereit sind, die kurze und einfache Erklärung zu verstehen, die in dem Artikel enthalten ist.


Der Autor von Closures hat Schließungen ziemlich gut erklärt, den Grund erklärt, warum wir sie brauchen, und auch LexicalEnvironment, das zum Verständnis von Schließungen notwendig ist.
Hier ist die Zusammenfassung:

Was ist, wenn auf eine Variable zugegriffen wird, diese aber nicht lokal ist? Wie hier:

In diesem Fall findet der Interpreter die Variable im äußeren LexicalEnvironmentObjekt.

Der Prozess besteht aus zwei Schritten:

  1. Wenn eine Funktion f erstellt wird, wird sie nicht in einem leeren Bereich erstellt. Es gibt ein aktuelles LexicalEnvironment-Objekt. Im obigen Fall ist es das Fenster (a ist zum Zeitpunkt der Funktionserstellung undefiniert).

Wenn eine Funktion erstellt wird, erhält sie eine verborgene Eigenschaft namens [[Scope]], die auf die aktuelle LexicalEnvironment verweist.

Wenn eine Variable gelesen wird, aber nirgendwo gefunden werden kann, wird ein Fehler generiert.

Verschachtelte Funktionen

Funktionen können ineinander verschachtelt werden und bilden eine Kette von LexicalEnvironments, die auch als Scope-Chain bezeichnet werden kann.

Funktion g hat also Zugriff auf g, a und f.

Verschlüsse

Eine verschachtelte Funktion kann weiterleben, nachdem die äußere Funktion abgeschlossen ist:

Markieren von Lexikalischen Umgebungen:

Wie wir sehen, this.sayist eine Eigenschaft im Benutzerobjekt, also lebt es weiter, nachdem der Benutzer abgeschlossen ist.

Wenn Sie sich erinnern, wann sie this.sayerstellt wird, erhält sie (wie jede Funktion) einen internen Verweis this.say.[[Scope]]auf die aktuelle LexicalEnvironment. Die LexicalEnvironment der aktuellen User-Ausführung bleibt also im Speicher. Alle Variablen des Benutzers sind auch seine Eigenschaften. Daher werden sie auch sorgfältig aufbewahrt und nicht wie üblich als Junk-Objekte verwendet.

Es geht darum, sicherzustellen, dass die innere Funktion in der Lage ist, auf eine äußere Variable zuzugreifen.

Zusammenfassen:

  1. Die innere Funktion enthält einen Verweis auf die äußere LexicalEnvironment.
  2. Die innere Funktion kann jederzeit auf Variablen von ihr zugreifen, auch wenn die äußere Funktion beendet ist.
  3. Der Browser speichert die LexicalEnvironment und alle ihre Eigenschaften (Variablen) im Speicher, bis eine innere Funktion vorhanden ist, die auf sie verweist.

Dies wird als Schließung bezeichnet.


Okay, im Gespräch mit einem 6-jährigen Kind würde ich möglicherweise folgende Assoziationen verwenden.

Stellen Sie sich vor, Sie spielen mit Ihren kleinen Brüdern und Schwestern im gesamten Haus, und Sie bewegen sich mit Ihren Spielsachen herum und bringen einige von ihnen in das Zimmer Ihres älteren Bruders. Nach einer Weile kam Ihr Bruder von der Schule zurück und ging in sein Zimmer, und er sperrte ihn ein, so dass Sie jetzt keine direkten Spielsachen mehr finden konnten. Aber Sie könnten an die Tür klopfen und Ihren Bruder nach dem Spielzeug fragen. Dies wird als Schließung von Spielzeug bezeichnet . Ihr Bruder hat es für Sie gemacht, und er ist jetzt in äußerster Reichweite .

Vergleichen Sie es mit einer Situation, in der eine Tür durch Zugluft gesperrt wurde und sich niemand darin befand (allgemeine Funktionsausführung). Dann kommt es zu örtlichen Bränden, die den Raum niederbrennen (Garbage Collector: D). Dann wurde ein neuer Raum errichtet und Sie können jetzt gehen ein anderes Spielzeug dort (neue Funktionsinstanz), aber nie die gleichen Spielsachen bekommen, die in der ersten Rauminstanz übrig waren.

Für ein fortgeschrittenes Kind würde ich etwas wie das Folgende setzen. Es ist nicht perfekt, aber man fühlt, was es ist:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Wie Sie sehen, sind die im Zimmer verbleibenden Spielsachen immer noch über den Bruder zugänglich, unabhängig davon, ob der Raum abgeschlossen ist. Hier ist ein Jsbin , um damit zu spielen.


Die Kinder werden sich immer an die Geheimnisse erinnern, die sie mit ihren Eltern geteilt haben, sogar nachdem ihre Eltern gegangen sind. Dies sind die Schließungen für Funktionen.

Die Geheimnisse für JavaScript-Funktionen sind die privaten Variablen

var parent = function() {
 var name = "Mary"; // secret
}

Bei jedem Aufruf wird die lokale Variable "Name" erstellt und der Name "Mary" gegeben. Jedes Mal, wenn die Funktion beendet wird, geht die Variable verloren und der Name wird vergessen.

Wie Sie sich vorstellen können, da die Variablen bei jedem Aufruf der Funktion neu erstellt werden und niemand sie kennt, muss es einen geheimen Ort geben, an dem sie gespeichert werden. Es könnte als Kammer der Geheimnisse oder Stack oder lokaler Geltungsbereich bezeichnet werden, aber es spielt keine Rolle. Wir wissen, dass sie irgendwo in der Erinnerung versteckt sind.

In JavaScript gibt es jedoch das Besondere, dass Funktionen, die in anderen Funktionen erstellt werden, auch die lokalen Variablen ihrer Eltern kennen und diese so lange behalten können, wie sie leben.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Solange wir uns in der übergeordneten Funktion befinden, können eine oder mehrere untergeordnete Funktionen erstellt werden, die die geheimen Variablen des geheimen Bereichs gemeinsam nutzen.

Aber das Traurige ist, wenn das Kind auch eine private Variable seiner Elternfunktion ist, würde es auch sterben, wenn das Elternteil endet, und die Geheimnisse würden mit ihnen sterben.

Um zu leben, muss das Kind gehen, bevor es zu spät ist

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

Und jetzt, obwohl Mary "nicht mehr läuft", ist die Erinnerung an sie nicht verloren und ihr Kind wird sich immer an ihren Namen und andere Geheimnisse erinnern, die sie während ihrer gemeinsamen Zeit geteilt haben.

Wenn Sie das Kind also "Alice" nennen, wird es antworten

child("Alice") => "My name is Alice, child of Mary"

Das ist alles, was zu erzählen ist.


Wenn wir die Frage ernst nehmen, sollten wir herausfinden, wozu ein typischer Sechsjähriger kognitiv fähig ist, obgleich jemand, der sich für JavaScript interessiert, nicht so typisch ist.

Zur Entwicklung der Kindheit: 5 bis 7 Jahre heißt es:

Ihr Kind kann in zwei Schritten folgen. Wenn Sie beispielsweise zu Ihrem Kind sagen: "Gehen Sie in die Küche und holen Sie mir einen Müllsack", kann sich diese Richtung merken.

Wir können dieses Beispiel verwenden, um Schließungen wie folgt zu erklären:

Die Küche ist eine Schließung mit einer lokalen Variablen, die als trashBags . In der Küche gibt es eine Funktion namens getTrashBag , die einen Müllsack erhält und ihn zurückgibt.

Wir können dies 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();

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

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

  • Bei jedem makeKitchen() wird ein neuer Abschluss mit eigenen trashBags .
  • Die trashBags Variable befindet sich lokal in jeder Küche und ist außerhalb nicht zugänglich. Die innere Funktion der getTrashBag Eigenschaft hat jedoch Zugriff darauf.
  • Jeder Funktionsaufruf erzeugt eine Schließung, aber es besteht keine Notwendigkeit, die Schließung herumzuhalten, es sei denn, eine innere Funktion, die Zugang zum Inneren der Schließung hat, kann außerhalb der Schließung aufgerufen werden. Die Rückgabe des Objekts mit der Funktion getTrashBag erfolgt hier.

JavaScript-Verschlüsse für Anfänger

Eingereicht von Morris am Di, 2006-02-21 10:19. Community-Redaktion seit.

Verschlüsse sind keine Zauberei

Auf dieser Seite werden Verschlüsse erläutert, damit ein Programmierer sie verstehen kann - mit funktionierendem JavaScript-Code. Es ist nicht für Gurus oder funktionale Programmierer.

Verschlüsse sind nicht schwer zu verstehen, sobald das Kernkonzept begründet ist. Sie sind jedoch durch das Lesen theoretischer oder wissenschaftlich orientierter Erklärungen nicht zu verstehen!

Dieser Artikel richtet sich an Programmierer mit etwas Programmiererfahrung in einer Hauptsprache, die die folgende JavaScript-Funktion lesen können:

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

Zwei kurze Zusammenfassungen

  • Wenn eine Funktion (foo) andere Funktionen (bar und baz) deklariert, wird die in foo erstellte Familie von lokalen Variablen nicht gelöscht, wenn die Funktion beendet wird. Die Variablen werden für die Außenwelt lediglich unsichtbar. Foo kann daher die Funktionen bar und baz listig zurückgeben, und sie können weiterhin lesen, schreiben und miteinander kommunizieren durch diese geschlossene Familie von Variablen ("die Schließung"), mit der sich kein anderer einmischen kann, nicht einmal jemand, der anruft foo wieder in zukunft.

  • Eine Schließung ist eine Möglichkeit, erstklassige Funktionen zu unterstützen . Es ist ein Ausdruck, der auf Variablen innerhalb seines Gültigkeitsbereichs verweisen kann (wenn er zum ersten Mal deklariert wurde), einer Variablen zugewiesen werden kann, als Argument an eine Funktion übergeben oder als Funktionsergebnis zurückgegeben werden kann.

Ein Beispiel für eine Schließung

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 ein Verweis auf eine Funktion auf eine Variable ( say2 ) im obigen Code zurückgegeben wird. Wenn Sie dies nicht tun, müssen Sie das betrachten, bevor Sie Schließungen lernen können. Ein Programmierer, der C verwendet, würde sich say2 , dass die Funktion einen Zeiger auf eine Funktion say2 , und dass die Variablen say und say2 jeweils ein Zeiger auf eine Funktion waren.

Es gibt einen kritischen Unterschied zwischen einem C-Zeiger auf eine Funktion und einem JavaScript-Verweis auf eine Funktion. In JavaScript können Sie sich eine Funktionsreferenzvariable als einen Zeiger auf eine Funktion und einen versteckten Zeiger auf einen Abschluss vorstellen.

Der obige Code hat eine Schließung, da die anonyme Funktion function() { console.log(text); } function() { console.log(text); } wird in einer anderen Funktion deklariert, in diesem Beispiel sayHello2() . Wenn Sie das function in einer anderen Funktion verwenden, erstellen Sie in JavaScript eine Schließung.

In C und den meisten anderen gängigen Sprachen sind nach dem Zurückkehren einer Funktion nicht mehr alle lokalen Variablen verfügbar, da der Stack-Frame zerstört wird.

Wenn Sie in JavaScript eine Funktion innerhalb einer anderen Funktion deklarieren, können die lokalen Variablen der äußeren Funktion nach der Rückkehr von ihr zugänglich bleiben. Dies wird oben gezeigt, weil wir die Funktion say2() nachdem wir von sayHello2() . Beachten Sie, dass der von uns aufgerufene Code auf den variablen text verweist, der eine lokale Variable der Funktion sayHello2() .

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

Wenn wir die Ausgabe von say2.toString() , sehen wir, dass der Code sich auf den variablen text bezieht. Die anonyme Funktion kann auf text verweisen, der den Wert 'Hello Bob' da die lokalen Variablen von sayHello2() in einem Closure geheim gehalten wurden.

Das Geniale 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 Verschlüsse wirklich schwer zu verstehen, wenn Sie über sie lesen, aber wenn Sie einige Beispiele sehen, wird klar, wie sie funktionieren (es hat eine Weile gedauert). Ich empfehle, die Beispiele sorgfältig durchzuarbeiten, bis Sie wissen, wie sie funktionieren. Wenn Sie anfangen, Verschlüsse zu verwenden, ohne zu verstehen, wie sie funktionieren, würden Sie bald sehr seltsame Fehler verursachen.

Beispiel 3

Dieses Beispiel zeigt, dass die lokalen Variablen nicht kopiert werden - sie werden durch Verweis beibehalten. Es ist, als ob der Stack-Frame auch nach der äußeren Funktion im Speicher erhalten bleibt!

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 eine gemeinsame Referenz auf dieselbe Schließung, da sie alle in einem einzigen Aufruf von 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 gemeinsamen Zugriff auf dieselbe Schließung - die lokalen Variablen von setupSomeGlobals() als die drei Funktionen definiert wurden.

Wenn Sie im obigen Beispiel erneut setupSomeGlobals() aufrufen, wird ein neuer Abschluss (Stack-Frame!) Erstellt. Die alten gLogNumber , gIncreaseNumber und gSetNumber werden mit neuen Funktionen überschrieben, die den neuen Abschluss aufweisen. (Wenn Sie in JavaScript eine Funktion innerhalb einer anderen Funktion deklarieren, werden die inneren Funktionen immer dann neu erstellt, wenn die äußere Funktion aufgerufen wird.)

Beispiel 5

Dieses Beispiel zeigt, dass der Abschluss alle lokalen Variablen enthält, die vor dem Beenden in der äußeren Funktion deklariert wurden. Beachten Sie, dass die Variable alice tatsächlich nach der anonymen Funktion deklariert wird. Die anonyme Funktion wird zuerst deklariert. Wenn diese Funktion aufgerufen wird, kann sie auf die alice Variable zugreifen, da sich alice im selben Gültigkeitsbereich befindet (JavaScript macht das Variieren von Variablen ). sayAlice()() ruft auch direkt die von sayAlice() - es ist genau dasselbe wie zuvor, jedoch 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 sich die say Variable ebenfalls innerhalb der Schließung befindet und von jeder anderen Funktion, auf die in sayAlice() deklariert wurde, oder auf die rekursiv in der inside-Funktion zugegriffen werden kann.

Beispiel 6

Dies ist für viele Menschen ein echtes Problem. Sie müssen es also verstehen. Seien Sie sehr vorsichtig, wenn Sie eine Funktion innerhalb einer Schleife definieren: Die lokalen Variablen des Abschlusses verhalten sich möglicherweise nicht so, wie Sie vielleicht denken.

Damit Sie dieses Beispiel verstehen können, müssen Sie die Funktion "Variable Hissen" in Javascript 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 Zeile result.push( function() {console.log(item + ' ' + list[i])} fügt dem result-Array dreimal einen Verweis auf eine anonyme Funktion hinzu, wenn Sie mit anonymen Funktionen nicht so vertraut sind es gefällt mir:

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

Beachten Sie, dass beim Ausführen des Beispiels "item2 undefined" dreimal protokolliert wird! Dies liegt daran, dass es wie bei den vorherigen Beispielen nur eine einzige Schließung für die lokalen Variablen für buildList (die result , i und item ). Wenn die anonymen Funktionen in der Zeile fnlist[j]() aufgerufen werden; Sie verwenden alle denselben einzigen Abschluss und verwenden den aktuellen Wert für i und item innerhalb dieses einen Abschlusses (wobei i Wert 3 da die Schleife abgeschlossen wurde und item Wert 'item2' ). Beachten Sie, dass wir von 0 aus item2 item Wert von item2 . Und das i ++ erhöht i auf den Wert 3 .

Es kann hilfreich sein zu sehen, was passiert, wenn eine Deklaration auf Blockebene des variablen item (über das Schlüsselwort let ) anstelle einer Variablendeklaration mit Funktionsumfang über das Schlüsselwort var . Wenn diese Änderung vorgenommen wird, hat jede anonyme Funktion im Array- result einen eigenen Abschluss. Wenn das Beispiel ausgeführt wird, lautet die Ausgabe wie folgt:

item0 undefined
item1 undefined
item2 undefined

Wenn die Variable i auch mit let anstelle von var , lautet die Ausgabe:

item0 1
item1 2
item2 3

Beispiel 7

In diesem letzten Beispiel wird bei jedem Aufruf der Hauptfunktion ein separater Abschluss erstellt.

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 das Beste, mit den Beispielen zu spielen. Das Lesen einer Erklärung ist viel schwieriger als das Verstehen von Beispielen. Meine Erklärungen zu Schließungen und Stackframes usw. sind technisch nicht korrekt - sie sind grobe Vereinfachungen, die das Verständnis erleichtern sollen. Wenn die Grundidee erst einmal fertig ist, können Sie die Details später abholen.

Schlusspunkte:

  • Wenn Sie die function in einer anderen Funktion verwenden, wird ein Verschluss verwendet.
  • Wenn Sie eval() innerhalb einer Funktion verwenden, wird ein Verschluss verwendet. Der Text, den Sie eval kann auf lokale Variablen der Funktion verweisen. Innerhalb von eval Sie sogar neue lokale Variablen erstellen, indem Sie eval('var foo = …')
  • Wenn Sie eine new Function(…) (den Funktionskonstruktor ) in einer Funktion verwenden, wird keine Schließung erstellt. (Die neue Funktion kann nicht auf die lokalen Variablen der äußeren Funktion verweisen.)
  • Eine Schließung in JavaScript ist eine Kopie aller lokalen Variablen, genau wie beim Beenden einer Funktion.
  • Es ist wahrscheinlich am besten zu glauben, dass ein Abschluss immer nur als Eintrag einer Funktion erstellt wird und die lokalen Variablen zu diesem Abschluss hinzugefügt werden.
  • Ein neuer Satz lokaler Variablen wird bei jedem Aufruf einer Funktion mit einer Schließung beibehalten (vorausgesetzt, die Funktion enthält eine Funktionsdeklaration), und es wird entweder eine Referenz auf die Funktion inside zurückgegeben oder in irgendeiner Weise eine externe Referenz für sie behalten ).
  • Zwei Funktionen sehen möglicherweise so aus, als hätten sie denselben Quelltext, verhalten sich jedoch aufgrund ihres "versteckten" Schließens völlig anders. 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')); ), funktioniert es nicht, wenn myFunction eine Schließung ist ( Natürlich würden Sie niemals daran denken, zur Laufzeit Quellcode-Zeichenfolgen zu ersetzen, aber ...).
  • Funktionsdeklarationen können innerhalb von Funktionsdeklarationen in Funktionen & mdash abgerufen werden, und Sie können Schließungen auf mehreren Ebenen erhalten.
  • Ich denke normalerweise, dass ein Abschluss sowohl für die Funktion als auch für die erfassten Variablen ein Begriff ist. Beachten Sie, dass ich diese Definition in diesem Artikel nicht verwende!
  • Ich vermute, dass Verschlüsse in JavaScript sich von denen unterscheiden, die normalerweise in funktionalen Sprachen zu finden sind.

Links

Vielen Dank

Wenn Sie gerade Schließungen gelernt haben (hier oder anderswo!), Bin ich an jeglichem Feedback von Ihnen zu Änderungen, die Sie vorschlagen könnten, hilfreich, um diesen Artikel klarer zu machen. Senden Sie eine E-Mail an morrisjohns.com (morris_closure @). Bitte beachten Sie, dass ich kein Javascript-Experte bin - auch keine Schließungen.

Originalbeitrag von Morris ist im Internetarchiv zu finden.


Du schläfst ein und du lädst Dan ein. Du sagst Dan, er soll einen XBox-Controller mitbringen.

Dan lädt Paul ein. Dan bittet Paul, einen Controller mitzubringen. Wie viele Controller wurden zur Party gebracht?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

Wikipedia zu Schließungen :

In der Informatik ist eine Schließung eine Funktion zusammen mit einer Referenzierungsumgebung für die nichtlokalen Namen (freie Variablen) dieser Funktion.

Technisch gesehen , in JavaScript , ist jede Funktion ein Verschluss . Es hat immer Zugriff auf Variablen, die im Umgebungsbereich definiert sind.

Da Umfang definierende Konstruktion in JavaScript eine Funktion ist , kein Codeblock wie in vielen anderen Sprachen, was wir meinen , in der Regel durch Schließung in JavaScript ist eine Funktion mit nicht - lokalen Variablen arbeitet in bereits ausgeführt umgebender Funktion definiert .

Verschlüsse werden häufig zum Erstellen von Funktionen mit verborgenen privaten Daten verwendet (dies ist jedoch nicht immer der Fall).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

Das obige Beispiel verwendet eine anonyme Funktion, die einmal ausgeführt wurde. Das muss aber nicht sein. Sie kann benannt (z. B. mkdb) und später ausgeführt werden, wobei bei jedem Aufruf eine Datenbankfunktion generiert wird. Jede generierte Funktion hat ein eigenes verstecktes Datenbankobjekt. Ein anderes Anwendungsbeispiel für Schließungen ist, wenn wir keine Funktion zurückgeben, sondern ein Objekt, das mehrere Funktionen für unterschiedliche Zwecke enthält, wobei jede dieser Funktionen Zugriff auf dieselben Daten hat.


Ein Verschluss ähnelt einem Objekt. Es wird instanziiert, wenn Sie eine Funktion aufrufen.

Der Gültigkeitsbereich eines Abschlusses in JavaScript ist lexikalisch. Dies bedeutet, dass alles, was in der Funktion enthalten ist, zu der der Abschluss gehört, Zugriff auf alle darin enthaltenen Variablen hat.

Eine Variable ist in der Schließung enthalten, wenn Sie

  1. ordnen Sie es mit var foo=1;oder zu
  2. einfach schreiben var foo;

Wenn eine innere Funktion (eine Funktion, die in einer anderen Funktion enthalten ist) auf eine solche Variable zugreift, ohne sie mit var in ihrem eigenen Bereich zu definieren, ändert sie den Inhalt der Variablen im äußeren Abschluss .

Eine Schließung überlebt die Laufzeit der Funktion, die sie erzeugt hat. Wenn andere Funktionen aus dem Abschluss / Bereich, in dem sie definiert sind (z. B. als Rückgabewerte), herauskommen , verweisen diese weiterhin auf diesen Abschluss .

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

Ich weiß, dass es bereits viele Lösungen gibt, aber ich denke, dass dieses kleine und einfache Skript nützlich sein kann, um das Konzept zu demonstrieren:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

OK, 6 Jahre alter Ventilator. Möchten Sie das einfachste Beispiel für die Schließung hören?

Stellen wir uns die nächste Situation vor: Ein Fahrer sitzt im Auto. Das Auto ist in einem Flugzeug. Flugzeug ist im Flughafen. Die Möglichkeit 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 sind, schauen Sie sich die detailliertere Erklärung oder das Beispiel unten an.

So kann ich meine Flugzeuggeschichte in Code umwandeln.

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


Ich verstehe nicht, warum die Antworten hier so komplex sind.

Hier ist eine Schließung:

var a = 42;

function b() { return a; }

Ja. Sie verwenden wahrscheinlich so oft am Tag.


Es gibt keinen Grund zu der Annahme, dass Schließungen ein komplexer Design-Hack sind, um spezifische Probleme anzugehen. Nein, bei Closures wird nur eine Variable verwendet, die aus einem höheren Bereich stammt, wenn die Funktion deklariert wurde (nicht ausgeführt) .

Nun , was es ermöglicht , was Sie tun können spektakuläre, Andere Antworten sein.


Verschlüsse sind einfach:

Das folgende einfache Beispiel behandelt alle Hauptaspekte von JavaScript-Verschlüssen. *

Hier ist eine Fabrik, die Taschenrechner produziert, die Folgendes hinzufügen und multiplizieren können:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Der Schlüsselpunkt: Jeder Aufruf an make_calculatorerstellt eine neue lokale Variable n, die von diesem Rechner weiterhin verwendet werden kann addund multiplylange nach der make_calculatorRückkehr funktioniert .

Wenn Sie mit Stack-Frames vertraut sind, wirken diese Rechner seltsam: Wie können sie nnach der make_calculatorRückkehr weiterhin darauf zugreifen ? Die Antwort ist, sich vorzustellen, dass JavaScript keine "Stack-Frames" verwendet, sondern "Heap-Frames", die nach dem Funktionsaufruf, der sie zurückgegeben hat, bestehen bleiben können.

Innere Funktionen wie addund multiply, die auf in einer äußeren Funktion ** deklarierte Variablen zugreifen , werden Schließungen genannt .

Das ist so ziemlich alles für Schließungen.


* Zum Beispiel werden alle Punkte des Artikels "Closures for Dummies" behandelt, die in einer anderen Antwort enthalten sind , mit Ausnahme von Beispiel 6, in dem lediglich angegeben wird, dass Variablen verwendet werden können, bevor sie deklariert werden. Dies ist eine schöne Tatsache, die jedoch völlig unabhängig von Closures ist. Es deckt auch alle Punkte in der akzeptierten Antwort ab , mit Ausnahme der Punkte (1), die Funktionen verwenden, um ihre Argumente in lokale Variablen (die benannten Funktionsargumente) zu kopieren, und (2) das Kopieren von Zahlen eine neue Nummer erzeugt, aber eine Objektreferenz kopiert gibt Ihnen einen anderen Verweis auf dasselbe Objekt. Diese sind auch gut zu wissen, aber auch völlig unabhängig von Schließungen. Es ist dem Beispiel in dieser Antwort ebenfalls sehr ähnlich, jedoch etwas kürzer und weniger abstrakt. Es geht nicht um den PunktDiese Antwort oder dieser Kommentar , der besagt , dass JavaScript das Einstecken des Stroms erschwertWert einer Schleifenvariablen in Ihre innere Funktion: Der Schritt "Einstecken" kann nur mit einer Hilfsfunktion erfolgen, die Ihre innere Funktion einschließt und bei jeder Schleifeniteration aufgerufen wird. (Streng genommen greift die innere Funktion auf die Kopie der Variablen der Hilfsfunktion zu, anstatt irgendetwas angeschlossen zu haben.) Wiederum sehr nützlich beim Erstellen von Schließungen, aber nicht Teil dessen, was eine Schließung ist oder wie sie funktioniert. Zusätzliche Verwirrung besteht darin, dass Schließungen in funktionalen Sprachen wie ML unterschiedlich funktionieren, wobei Variablen nicht an Speicherplatz, sondern an Werte gebunden sind, wodurch ein ständiger Strom von Personen bereitgestellt wird, die Schließungen auf eine Weise verstehen (nämlich "Einstecken") einfach falsch für JavaScript, wo Variablen immer an Speicherplatz gebunden sind und niemals an Werte.

** Jede äußere Funktion, wenn mehrere verschachtelt sind oder sogar im globalen Kontext, wie diese Antwort deutlich macht.


Verschlüsse sind schwer zu erklären, da sie dazu dienen, Verhaltensweisen zu schaffen, von denen jeder intuitiv erwartet, dass sie trotzdem funktionieren. Ich finde, der beste Weg, sie zu erklären (und die Art und Weise, wie ich gelernt habe, 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 keine Schließungen kennt? Ersetzen Sie einfach den Aufruf in der letzten Zeile durch seinen Methodenkörper (was Funktionsaufrufe grundsätzlich tun), und Sie erhalten Folgendes:

console.log(x + 3);

Wo ist die Definition von x ? Wir haben es im aktuellen Umfang nicht definiert. Die einzige Lösung besteht darin, dass plus5 seinen Geltungsbereich (oder vielmehr den Geltungsbereich des übergeordneten plus5 tragen lässt . Auf diese Weise ist x klar definiert und an den Wert 5 gebunden.


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

Wie der alte Albert sagte: "Wenn Sie es einem Sechsjährigen nicht erklären können, verstehen Sie es wirklich selbst nicht." Nun, ich habe versucht, einem 27-jährigen Freund die Schließungen von JS zu erklären, und war total gescheitert.

Kann irgendjemand meinen, ich bin 6 und habe ein seltsames Interesse an diesem Thema?

Ich bin mir ziemlich sicher, dass ich einer der wenigen war, der versucht hat, die ursprüngliche Frage wörtlich zu nehmen. Seitdem hat sich die Frage mehrmals verändert, sodass meine Antwort jetzt unglaublich dumm und unpassend erscheint. Hoffentlich macht die generelle Idee der Geschichte für manche Spaß.

Ich bin ein großer Fan von Analogie und Metapher, wenn es darum geht, schwierige Konzepte zu erklären. Lassen Sie mich also eine Geschichte ausprobieren.

Es war einmal:

Es war eine Prinzessin ...

function princess() {

Sie lebte in einer wundervollen Welt voller Abenteuer. Sie traf ihren Prince Charming, fuhr auf einem Einhorn um ihre Welt, kämpfte gegen Drachen, traf sprechende Tiere und viele andere fantastische Dinge.

    var adventures = [];

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

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

    /* ... */

Aber sie musste immer wieder in ihre langweilige Arbeitswelt und Erwachsene zurückkehren.

    return {

Und sie erzählte ihnen oft von ihrem neuesten erstaunlichen 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 Einhörner oder Drachen glauben, weil sie sie niemals sehen könnten. Die Erwachsenen sagten, dass sie nur in der Vorstellung des kleinen Mädchens existierten.

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

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


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, da bar auf das x zugreifen kann, das als Argument für foo , und es kann auch von foo auf tmp zugreifen.

Das ist eine Schließung. Eine Funktion muss nicht zurückkehren , um als Schließung bezeichnet zu werden. Durch den einfachen Zugriff auf Variablen außerhalb Ihres unmittelbaren lexikalischen Bereichs wird eine Schließung erstellt .

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 auch 16, da der bar immer noch auf x und tmp verweisen kann, obwohl er sich nicht mehr direkt im Gültigkeitsbereich befindet.

Da tmp jedoch immer noch im Verschluss der bar hängt, wird es ebenfalls inkrementiert. Sie wird bei jedem Aufruf der bar inkrementiert.

Das einfachste Beispiel für einen Abschluss ist:

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. Dieser Ausführungskontext empfängt zusammen mit den Funktionsargumenten und dem übergeordneten Objekt auch alle außerhalb davon deklarierten Variablen (im obigen Beispiel sowohl 'a' als auch 'b').

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

Hier ist die Zahl x eine Literalzahl. Wie bei anderen Literalen in JavaScript wird beim Aufruf von foo die Zahl x als Argument x in foo kopiert .

Andererseits verwendet JavaScript immer Referenzen, wenn es um Objekte geht. Wenn Sie beispielsweise foo mit einem Objekt aufgerufen haben, referenziert die zurückgegebene Schließung dieses 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, wird jeder Aufruf von bar(10) x.memb erhöht. Was nicht erwartet werden kann, ist, dass x sich auf dasselbe Objekt bezieht wie die Altersvariable! Nach ein paar Telefonanrufen wird age.memb 2 Jahre age.memb ! Diese Referenzierung ist die Basis für Speicherverluste bei HTML-Objekten.


Wie würde ich es einem Sechsjährigen erklären:

Sie wissen, wie Erwachsene ein Haus besitzen können, und sie nennen es Zuhause? Wenn eine Mutter ein Kind hat, besitzt das Kind wirklich nichts, oder? Aber seine Eltern besitzen ein Haus. Wenn also jemand das Kind fragt "Wo ist dein Zuhause?", Kann er / sie "das Haus!" Beantworten und auf das Haus seiner Eltern zeigen. Eine "Schließung" ist die Fähigkeit des Kindes, immer (auch wenn im Ausland) sagen zu können, dass es ein Zuhause hat, obwohl es eigentlich die Eltern sind, die das Haus besitzen.


Nun, das Problem ist, dass die Variable i innerhalb jeder Ihrer anonymen Funktionen an dieselbe Variable außerhalb der Funktion gebunden ist.

Klassische Lösung: Verschlüsse

Sie möchten die Variable innerhalb jeder Funktion an einen separaten, unveränderlichen Wert außerhalb der Funktion binden:

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Da es in JavaScript - nur Funktionsumfang - keinen Blockbereich gibt, indem Sie die Funktionserstellung in eine neue Funktion einbetten, stellen Sie sicher, dass der Wert von "i" wie gewünscht bleibt.

2015 Lösung: forEach

Array.prototype.forEach der relativ weit verbreiteten Funktion Array.prototype.forEach (2015) ist es erwähnenswert, dass .forEach() in solchen Situationen, in denen die Iteration hauptsächlich über eine Reihe von Werten erfolgt, eine saubere und natürliche Möglichkeit bietet, einen eindeutigen Abschluss zu erhalten jede Wiederholung. Vorausgesetzt, Sie haben eine Art Array mit Werten (DOM-Referenzen, Objekte, was auch immer) und das Problem, Callbacks für jedes Element einzurichten, können Sie folgendermaßen tun:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

Die Idee ist, dass jeder Aufruf der Callback-Funktion, die mit der .forEach Schleife verwendet wird, seine eigene Schließung ist. Der an diesen Handler übergebene Parameter ist das für diesen bestimmten Schritt der Iteration spezifische Arrayelement. Wenn es in einem asynchronen Callback verwendet wird, kollidiert es nicht mit den anderen Callbacks, die an anderen Schritten der Iteration erstellt wurden.

Wenn Sie mit jQuery arbeiten, bietet Ihnen die Funktion $.each() eine ähnliche Funktion.

ES6-Lösung: let

ECMAScript 6 (ES6), die neueste Version von JavaScript, wird nun in vielen Evergreen-Browsern und Backend-Systemen implementiert. Es gibt auch Transpiler wie Babel , die ES6 in ES5 konvertieren, um die Verwendung neuer Funktionen auf älteren Systemen zu ermöglichen.

ES6 führt neue let- und const Schlüsselwörter ein, die sich anders als var basierte Variablen unterscheiden. In einer Schleife mit einem let basierten Index hat beispielsweise jede Iteration durch die Schleife einen neuen Wert von i wobei jeder Wert innerhalb der Schleife liegt, sodass Ihr Code wie erwartet funktioniert. Es gibt viele Ressourcen, aber ich würde den Block-Scoping-Beitrag von 2ality als großartige Informationsquelle empfehlen.

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

Beachten Sie jedoch, dass IE9-IE11 und Edge vor Edge 14 die Unterstützung let aber die obigen Fehler nicht beheben (sie erstellen nicht jedes Mal ein neues i , sodass alle oben genannten Funktionen 3 protokollieren würden, wenn wir var ). Rand 14 hat es endlich geschafft.





javascript function variables scope closures