stackoverflow javascript




Wie funktionieren JavaScript-Verschlüsse? (20)

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.

https://code.i-harness.com


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.


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)


Können Sie einem 5-Jährigen Schließungen erklären? *

Ich finde immer noch, dass Googles Erklärung sehr gut funktioniert und kurz ist:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

* AC # Frage


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.


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.


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.


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.

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.


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

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.


JavaScript-Funktionen können auf Folgendes zugreifen:

  1. Argumente
  2. Einheimische (dh ihre lokalen Variablen und lokalen Funktionen)
  3. Umwelt, die Folgendes beinhaltet:
    • Globals, einschließlich des DOM
    • alles in äußeren Funktionen

Wenn eine Funktion auf ihre Umgebung zugreift, ist die Funktion eine Schließung.

Beachten Sie, dass äußere Funktionen nicht erforderlich sind, obwohl sie Vorteile bieten, die ich hier nicht bespreche. Durch den Zugriff auf Daten in seiner Umgebung bleiben diese Daten durch eine Schließung erhalten. Im Unterfall der äußeren / inneren Funktionen kann eine äußere Funktion lokale Daten erzeugen und schließlich beenden. Wenn jedoch eine innere Funktion (s) nach dem Beenden der äußeren Funktion überleben, behalten die inneren Funktionen die lokalen Daten der äußeren Funktion am Leben.

Beispiel für einen Abschluss, der die globale Umgebung verwendet:

Stellen Sie sich vor, dass die Ereignisse zum Abstapeln der Stack-Überlauf- und Abstimmungsschaltflächen als Schließungen (voteUp_click und voteDown_click) implementiert werden, die Zugriff auf die externen Variablen isVotedUp und isVotedDown haben, die global definiert sind. (Der Einfachheit halber beziehe ich mich auf die Frage-Abstimmung-Schaltflächen von , nicht auf die Anordnung der Antwort-Antwort-Schaltflächen.)

Wenn der Benutzer auf die VoteUp-Schaltfläche klickt, prüft die voteUp_click-Funktion, ob isVotedDown == true ist, um zu bestimmen, ob eine Abstimmung oder ein Abbruch einer Abstimmung durchgeführt werden soll. Die Funktion voteUp_click ist eine Schließung, da auf seine Umgebung zugegriffen wird.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Bei allen vier Funktionen handelt es sich um Sperren, da sie alle auf ihre Umgebung zugreifen.


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.


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.


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.


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

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

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

Ich habe ein interaktives JavaScript-Tutorial zusammengestellt, um zu erklären, wie Schließungen funktionieren. Was ist eine Schließung?

Hier ist eines der Beispiele:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

Ich lerne in der Regel besser durch gute / schlechte Vergleiche. Ich möchte gerne Arbeitscode, gefolgt von nicht funktionierendem Code, dem jemand begegnet. Ich habe ein jsFiddle zusammengestellt , das einen Vergleich durchführt und versucht, die Unterschiede auf die einfachsten Erklärungen zu reduzieren , die mir einfallen könnten.

Schließungen richtig gemacht:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • In dem obigen Code createClosure(n)wird in jeder Iteration der Schleife Code aufgerufen. Beachten Sie, dass ich die Variable benannt habe, num hervorzuheben, dass es sich um eine neue Variable handelt, die in einem neuen Funktionsbereich erstellt wurde, und nicht die Variable indexist, die an den äußeren Bereich gebunden ist.

  • Dies schafft einen neuen Bereich und nist an diesen Bereich gebunden. Das heißt, wir haben 10 separate Bereiche, einen für jede Iteration.

  • createClosure(n) gibt eine Funktion zurück, die das n innerhalb dieses Bereichs zurückgibt.

  • Innerhalb jedes Bereichs nist der Wert an den Wert gebunden, zu dem es createClosure(n)aufgerufen wurde, sodass die verschachtelte Funktion, die zurückgegeben wird, immer den Wert desjenigen zurückgibt n, zu dem sie createClosure(n)aufgerufen wurde.

Abschlüsse falsch gemacht:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • Im obigen Code wurde die Schleife innerhalb der createClosureArray()Funktion verschoben und die Funktion gibt jetzt nur das vollständige Array zurück, was auf den ersten Blick intuitiver erscheint.

  • Was nicht offensichtlich ist, ist, dass, da createClosureArray()nur einmal aufgerufen wird, nur ein Gültigkeitsbereich für diese Funktion erstellt wird, anstatt einen für jede Iteration der Schleife.

  • Innerhalb dieser Funktion wird eine Variable mit dem Namen indexdefiniert. Die Schleife wird ausgeführt und fügt dem Array zurückgegebene Funktionen hinzu index. Beachten Sie, dass dies indexinnerhalb der createClosureArrayFunktion definiert ist, die immer nur einmal aufgerufen wird.

  • Da es nur einen Bereich innerhalb der createClosureArray()Funktion gab, indexist er nur an einen Wert innerhalb dieses Bereichs gebunden. Mit anderen Worten, jedes Mal, wenn die Schleife den Wert ändert index, ändert sie den Wert für alles, was auf diesen Bereich innerhalb dieses Bereichs verweist.

  • Alle dem Array hinzugefügten Funktionen geben die SAME- indexVariable aus dem übergeordneten Bereich zurück, in der sie definiert wurde, anstelle von 10 verschiedenen aus 10 verschiedenen Bereichen, wie im ersten Beispiel. Das Endergebnis ist, dass alle 10 Funktionen dieselbe Variable aus demselben Bereich zurückgeben.

  • Nachdem die Schleife beendet und indexdie Änderung abgeschlossen war, war der Endwert 10, daher gibt jede zum Array hinzugefügte Funktion den Wert der einzelnen indexVariablen zurück, die jetzt auf 10 gesetzt ist.

Ergebnis

SCHLIESSUNGEN NACH RECHTS
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

VERSCHLÜSSE VERSCHLÜSSE
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


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.







closures