7619 Stimmen

Wie funktionieren die JavaScript-Schließungen?

Wie würden Sie JavaScript-Schließungen jemandem erklären, der zwar die Konzepte kennt, aus denen sie bestehen (z. B. Funktionen, Variablen und Ähnliches), aber die Schließungen selbst nicht versteht?

Ich habe gesehen das Beispiel Schema auf Wikipedia angegeben, aber leider hat es nicht geholfen.

58voto

dmi3y Punkte 3302

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

Stell dir vor, du spielst mit deinen kleinen Brüdern und Schwestern im ganzen Haus, und du bewegst dich mit deinem Spielzeug und bringst einige davon in das Zimmer deines älteren Bruders. Nach einer Weile kommt dein Bruder von der Schule zurück und geht in sein Zimmer, das er verschlossen hat, so dass du keinen direkten Zugang mehr zu den Spielsachen hast, die dort liegen. Aber du konntest an die Tür klopfen und deinen Bruder nach dem Spielzeug fragen. Das nennt man "Spielzeug". Verschluss Ihr Bruder hat es für Sie erfunden und ist jetzt in der Außenwelt. Umfang .

Vergleichen Sie das mit einer Situation, in der eine Tür durch einen Luftzug verschlossen war und sich niemand darin befand (allgemeine Funktionsausführung), und dann ein lokales Feuer auftrat und den Raum niederbrannte (Müllsammler:D), und dann ein neuer Raum gebaut wurde und Sie nun ein anderes Spielzeug dort lassen können (neue Funktionsinstanz), aber niemals das gleiche Spielzeug bekommen, das in der ersten Rauminstanz gelassen wurde.

Für ein fortgeschrittenes Kind würde ich etwas wie das Folgende schreiben. Es ist nicht perfekt, aber es gibt einem ein Gefühl dafür, 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 können, sind die im Zimmer zurückgelassenen Spielsachen immer noch über den Bruder zugänglich, auch wenn das Zimmer abgeschlossen ist. Hier ist a jsbin um damit herumzuspielen.

52voto

srgstm Punkte 3401

Eine Funktion in JavaScript ist nicht nur ein Verweis auf eine Reihe von Anweisungen (wie in der Sprache C), sondern sie enthält auch eine verborgene Datenstruktur, die aus Verweisen auf alle von ihr verwendeten nichtlokalen Variablen (captured variables) besteht. Solche zweiteiligen Funktionen werden als Closures bezeichnet. Jede Funktion in JavaScript kann als Closure betrachtet werden.

Closures sind Funktionen mit einem Status. Sie ähneln "this" in dem Sinne, dass "this" auch einen Zustand für eine Funktion bereitstellt, aber Funktion und "this" sind getrennte Objekte ("this" ist nur ein ausgefallener Parameter, und die einzige Möglichkeit, ihn dauerhaft an eine Funktion zu binden, besteht darin, eine Closure zu erstellen). Während "this" und die Funktion immer getrennt leben, kann eine Funktion nicht von ihrer Schließung getrennt werden, und die Sprache bietet keine Möglichkeit, auf erfasste Variablen zuzugreifen.

Da all diese externen Variablen, auf die eine lexikalisch verschachtelte Funktion verweist, eigentlich lokale Variablen in der Kette ihrer lexikalisch umschließenden Funktionen sind (globale Variablen können als lokale Variablen einer Root-Funktion angenommen werden) und jede einzelne Ausführung einer Funktion neue Instanzen ihrer lokalen Variablen erzeugt, folgt daraus, dass jede Ausführung einer Funktion, die eine verschachtelte Funktion zurückgibt (oder anderweitig weiterleitet, z. B. als Callback registriert), eine neue Closure erzeugt (mit ihrem eigenen potenziell einzigartigen Satz referenzierter nichtlokaler Variablen, die ihren Ausführungskontext darstellen).

Außerdem muss man wissen, dass lokale Variablen in JavaScript nicht auf dem Stack-Frame, sondern auf dem Heap angelegt und erst dann zerstört werden, wenn niemand mehr auf sie verweist. Wenn eine Funktion zurückkehrt, werden die Verweise auf ihre lokalen Variablen dekrementiert, aber sie können immer noch nicht null sein, wenn sie während der aktuellen Ausführung Teil einer Closure wurden und immer noch von deren lexikalisch verschachtelten Funktionen referenziert werden (was nur passieren kann, wenn die Verweise auf diese verschachtelten Funktionen zurückgegeben oder anderweitig an einen externen Code übertragen wurden).

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

51voto

Stupid Stupid Punkte 205

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

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, verschwinden alle Variablen und Argumente, die in der Funktion verwendet wurden, die sie erstellt hat, nicht. Stattdessen wird die übergeordnete Funktion "geschlossen". Mit anderen Worten, nichts kann in sie hineinschauen und die von ihr verwendeten Variablen sehen, außer der Funktion, die sie zurückgibt. Diese neue Funktion hat die besondere 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 andere, sehr einfache Art, dies zu erklären, ist die des Umfangs:

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.

51voto

Michael Dziedzic Punkte 513

Vielleicht ein wenig über alle, aber die frühreifsten der Sechsjährigen, aber ein paar Beispiele, die dazu beigetragen, dass das Konzept der Schließung in JavaScript Klick für mich.

Eine Schließung ist eine Funktion, die Zugriff auf den Geltungsbereich einer anderen Funktion (ihre Variablen und Funktionen) hat. Der einfachste Weg, eine Closure zu erstellen, ist eine Funktion innerhalb einer Funktion; der Grund dafür ist, dass in JavaScript eine Funktion immer Zugriff auf den Bereich der sie enthaltenden Funktion hat.

function outerFunction() {
    var outerVar = "monkey";

    function innerFunction() {
        alert(outerVar);
    }

    innerFunction();
}

outerFunction();

ALERT: Affe

Im obigen Beispiel wird outerFunction aufgerufen, die wiederum innerFunction aufruft. Man beachte, dass outerVar für innerFunction verfügbar ist, was dadurch belegt wird, dass der Wert von outerVar korrekt gemeldet wird.

Überlegen Sie nun Folgendes:

function outerFunction() {
    var outerVar = "monkey";

    function innerFunction() {
        return outerVar;
    }

    return innerFunction;
}

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

ALERT: Affe

referenceToInnerFunction wird auf outerFunction() gesetzt, die einfach einen Verweis auf innerFunction zurückgibt. Wenn referenceToInnerFunction aufgerufen wird, gibt es outerVar zurück. Auch hier zeigt sich wieder, dass innerFunction Zugriff auf outerVar, eine Variable von outerFunction, hat. Interessant ist auch, dass sie diesen Zugriff auch nach Beendigung der Ausführung von outerFunction beibehält.

Und hier wird es wirklich interessant. Wenn wir outerFunction loswerden, sagen wir, es auf null setzen, könnte man meinen, dass referenceToInnerFunction seinen Zugriff auf den Wert von outerVar verlieren würde. Aber das ist nicht der Fall.

function outerFunction() {
    var outerVar = "monkey";

    function innerFunction() {
        return outerVar;
    }

    return innerFunction;
}

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

outerFunction = null;
alert(referenceToInnerFunction());

ALERT: Affe ALERT: Affe

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

Der Grund dafür, dass referenceToInnerFunction immer noch auf den Wert von outerVar zugreifen kann, liegt darin, dass innerFunction bei der ersten Erstellung der Closure durch Platzierung von innerFunction innerhalb von outerFunction einen Verweis auf den Bereich von outerFunction (seine Variablen und Funktionen) zu seiner Bereichskette hinzugefügt hat. Das bedeutet, dass innerFunction einen Zeiger oder Verweis auf alle Variablen von outerFunction hat, einschließlich outerVar. Selbst wenn die Ausführung von outerFunction beendet ist oder wenn sie gelöscht oder auf null gesetzt wird, verbleiben die Variablen in ihrem Bereich, wie outerVar, im Speicher, da der Verweis auf sie auf Seiten der innerFunction, die an referenceToInnerFunction zurückgegeben wurde, noch aussteht. Um outerVar und die übrigen Variablen von outerFunction wirklich aus dem Speicher freizugeben, müssten Sie diesen ausstehenden Verweis auf sie loswerden, indem Sie beispielsweise referenceToInnerFunction ebenfalls auf null setzen.

//////////

Zwei weitere Dinge zu den Schließungen sind zu beachten. Erstens hat die Schließung immer Zugriff auf die letzten Werte der sie enthaltenden Funktion.

function outerFunction() {
    var outerVar = "monkey";

    function innerFunction() {
        alert(outerVar);
    }

    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

ALERT: Gorilla

Zweitens behält eine Closure bei ihrer Erstellung einen Verweis auf alle Variablen und Funktionen der sie umschließenden Funktion bei; sie kann sich nicht alles aussuchen. Daher sollten Closures sparsam oder zumindest vorsichtig eingesetzt werden, da sie sehr speicherintensiv sein können; viele Variablen können noch lange nach der Ausführung einer enthaltenen Funktion im Speicher gehalten werden.

48voto

mjmoody383 Punkte 348

Ich würde sie einfach auf die Mozilla Schließungen Seite . Es ist das beste, am meisten knappe und einfache Erklärung der Grundlagen des Verschlusses und der praktischen Anwendung, die ich gefunden habe. Es ist sehr empfehlenswert für jeden, der JavaScript lernt.

Und ja, ich würde es sogar einem 6-Jährigen empfehlen - wenn ein 6-Jähriger etwas über Verschlüsse lernt, dann ist es logisch, dass er bereit ist, die knappe und einfache Erklärung in dem Artikel angegeben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X