Wie funktionieren JavaScript-Schließungen?

7636

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

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

Zaheer Ahmed
quelle
391
Mein Problem mit diesen und vielen Antworten ist, dass sie sich aus einer abstrakten, theoretischen Perspektive nähern, anstatt zunächst zu erklären, warum in Javascript Verschlüsse erforderlich sind und in welchen praktischen Situationen Sie sie verwenden. Am Ende haben Sie einen Artikel, den Sie ständig durchgehen müssen und der immer wieder denkt: "Aber warum?". Ich würde einfach damit beginnen: Verschlüsse sind eine gute Möglichkeit, mit den folgenden zwei Realitäten von JavaScript umzugehen: a. Der Geltungsbereich liegt auf der Funktionsebene, nicht auf der Blockebene, und b. Vieles, was Sie in der Praxis in JavaScript tun, ist asynchron / ereignisgesteuert.
Jeremy Burton
53
@Redsandro Zum einen erleichtert es das Schreiben von ereignisgesteuertem Code erheblich. Ich kann eine Funktion auslösen, wenn die Seite geladen wird, um Einzelheiten über den HTML-Code oder die verfügbaren Funktionen zu ermitteln. Ich kann einen Handler in dieser Funktion definieren und festlegen und alle Kontextinformationen jedes Mal verfügbar haben, wenn der Handler aufgerufen wird, ohne ihn erneut abfragen zu müssen. Lösen Sie das Problem einmal und verwenden Sie es auf jeder Seite, auf der dieser Handler benötigt wird, mit reduziertem Overhead beim erneuten Aufrufen des Handlers. Haben Sie jemals gesehen, dass dieselben Daten zweimal in einer Sprache neu zugeordnet werden, in der sie nicht vorhanden sind? Verschlüsse machen es viel einfacher, solche Dinge zu vermeiden.
Erik Reppen
1
@Erik Reppen danke für die Antwort. Eigentlich war ich neugierig auf die Vorteile dieses schwer lesbaren closureCodes, Object Literalder sich selbst wiederverwendet und den Overhead trotzdem reduziert, aber 100% weniger Wrap-Code erfordert.
Redsandro
6
Für Java-Programmierer lautet die kurze Antwort: Es ist das Funktionsäquivalent einer inneren Klasse. Eine innere Klasse enthält auch einen impliziten Zeiger auf eine Instanz der äußeren Klasse und wird für den gleichen Zweck verwendet (dh zum Erstellen von Ereignishandlern).
Boris van Schooten
8
Ich fand dieses praktische Beispiel sehr nützlich: youtube.com/watch?v=w1s9PgtEoJs
Abhi

Antworten:

7358

Ein Verschluss ist eine Paarung von:

  1. Eine Funktion und
  2. Ein Verweis auf den äußeren Bereich dieser Funktion (lexikalische Umgebung)

Eine lexikalische Umgebung ist Teil jedes Ausführungskontexts (Stapelrahmen) und eine Zuordnung zwischen Bezeichnern (dh lokalen Variablennamen) und Werten.

Jede Funktion in JavaScript behält einen Verweis auf ihre äußere lexikalische Umgebung bei. Diese Referenz wird verwendet, um den Ausführungskontext zu konfigurieren, der beim Aufrufen einer Funktion erstellt wird. Diese Referenz ermöglicht es dem Code innerhalb der Funktion, außerhalb der Funktion deklarierte Variablen zu "sehen", unabhängig davon, wann und wo die Funktion aufgerufen wird.

Wenn eine Funktion von einer Funktion aufgerufen wurde, die wiederum von einer anderen Funktion aufgerufen wurde, wird eine Kette von Verweisen auf äußere lexikalische Umgebungen erstellt. Diese Kette wird als Scope-Kette bezeichnet.

Bildet im folgenden Code innereinen Abschluss mit der lexikalischen Umgebung des Ausführungskontexts, der beim fooAufrufen erstellt wird, und schließt über die Variable secret:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

Mit anderen Worten: In JavaScript enthalten Funktionen einen Verweis auf eine private "Statusbox", auf die nur sie (und alle anderen Funktionen, die in derselben lexikalischen Umgebung deklariert sind) Zugriff haben. Diese Statusbox ist für den Aufrufer der Funktion unsichtbar und bietet einen hervorragenden Mechanismus zum Ausblenden und Einkapseln von Daten.

Und denken Sie daran: Funktionen in JavaScript können wie Variablen (erstklassige Funktionen) weitergegeben werden, was bedeutet, dass diese Paarungen von Funktionalität und Status in Ihrem Programm weitergegeben werden können: Ähnlich wie Sie eine Instanz einer Klasse in C ++ weitergeben könnten.

Wenn JavaScript keine Abschlüsse hätte, müsste explizit mehr Status zwischen Funktionen übergeben werden , wodurch die Parameterlisten länger und der Code lauter werden.

Wenn Sie also möchten, dass eine Funktion immer Zugriff auf einen privaten Status hat, können Sie einen Abschluss verwenden.

... und häufig möchten wir einen Zustand mit einer Funktion verknüpfen. Wenn Sie beispielsweise in Java oder C ++ einer Klasse eine private Instanzvariable und eine Methode hinzufügen, ordnen Sie den Status der Funktionalität zu.

In C und den meisten anderen gängigen Sprachen sind nach der Rückkehr einer Funktion nicht mehr alle lokalen Variablen verfügbar, da der Stapelrahmen 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 dieser Funktion zugänglich bleiben. Auf diese Weise secretbleibt im obigen Code das Funktionsobjekt verfügbar inner, nachdem es von zurückgegeben wurde foo.

Verwendung von Verschlüssen

Schließungen sind immer dann nützlich, wenn Sie einen privaten Status benötigen, der einer Funktion zugeordnet ist. Dies ist ein sehr häufiges Szenario - und denken Sie daran: JavaScript hatte bis 2015 keine Klassensyntax und noch keine private Feldsyntax. Verschlüsse erfüllen diesen Bedarf.

Variablen für private Instanzen

Im folgenden Code wird die Funktion toStringüber die Details des Fahrzeugs geschlossen.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Funktionale Programmierung

Im folgenden Code wird die Funktion innerüber fnund geschlossen args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Ereignisorientierte Programmierung

Im folgenden Code wird die Funktion onClicküber der Variablen geschlossen BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

Modularisierung

Im folgenden Beispiel sind alle Implementierungsdetails in einem sofort ausgeführten Funktionsausdruck verborgen. Die Funktionen tickund toStringschließen über den privaten Staat und Funktionen, die sie benötigen, um ihre Arbeit abzuschließen. Durch Verschlüsse konnten wir unseren Code modularisieren und kapseln.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Beispiele

Beispiel 1

Dieses Beispiel zeigt, dass die lokalen Variablen nicht in den Abschluss kopiert werden: Der Abschluss behält einen Verweis auf die ursprünglichen Variablen selbst bei . Es ist, als ob der Stapelrahmen auch nach dem Beenden der äußeren Funktion im Speicher lebendig bleibt.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

Beispiel 2

Im folgenden Code, drei Methoden log, incrementund updatealle in der Nähe im gleichen lexikalischen Umgebung.

Und jedes Mal createObject, wenn aufgerufen wird, wird ein neuer Ausführungskontext (Stapelrahmen) erstellt und eine völlig neue Variable xsowie ein neuer Satz von Funktionen ( logusw.) erstellt, die über dieser neuen Variablen geschlossen werden.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

Beispiel 3

Wenn Sie Variablen verwenden var, die mit deklariert wurden , achten Sie darauf, dass Sie verstehen, über welche Variable Sie schließen. Mit deklarierte Variablen varwerden gehisst. Dies ist aufgrund der Einführung von letund in modernem JavaScript weitaus weniger problematisch const.

Im folgenden Code wird jedes Mal um die Schleife herum eine neue Funktion innererstellt, die geschlossen wird i. Da var idiese inneren Funktionen jedoch außerhalb der Schleife angehoben werden, schließen sie alle über dieselbe Variable, was bedeutet, dass der Endwert von i(3) dreimal gedruckt wird.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Endpunkte:

  • Immer wenn eine Funktion in JavaScript deklariert wird, wird ein Abschluss erstellt.
  • Die Rückgabe einer functionFunktion aus einer anderen Funktion ist das klassische Beispiel für einen Abschluss, da der Status innerhalb der äußeren Funktion der zurückgegebenen inneren Funktion implizit zur Verfügung steht, selbst nachdem die Ausführung der äußeren Funktion abgeschlossen wurde.
  • Immer wenn Sie eval()innerhalb einer Funktion verwenden, wird ein Verschluss verwendet. Der Text, auf den Sie evalauf lokale Variablen der Funktion verweisen können, und im nicht strengen Modus können Sie sogar neue lokale Variablen erstellen, indem Sie verwenden eval('var foo = …').
  • Wenn Sie new Function(…)(den Funktionskonstruktor ) in einer Funktion verwenden, wird diese nicht über ihre lexikalische Umgebung geschlossen, sondern über den globalen Kontext. Die neue Funktion kann nicht auf die lokalen Variablen der äußeren Funktion verweisen.
  • Ein Abschluss in JavaScript ist so, als würde ein Verweis ( NICHT eine Kopie) auf den Bereich am Punkt der Funktionsdeklaration beibehalten, der wiederum einen Verweis auf seinen äußeren Bereich usw. bis zum globalen Objekt oben auf behält die Scope-Kette.
  • Ein Abschluss wird erstellt, wenn eine Funktion deklariert wird. Dieser Abschluss wird verwendet, um den Ausführungskontext zu konfigurieren, wenn die Funktion aufgerufen wird.
  • Bei jedem Aufruf einer Funktion wird ein neuer Satz lokaler Variablen erstellt.

Links

Ben Aston
quelle
74
Das hört sich gut an: "Ein Abschluss in JavaScript ist so, als würde man eine Kopie aller lokalen Variablen behalten, so wie sie waren, als eine Funktion beendet wurde." Aber es ist aus mehreren Gründen irreführend. (1) Der Funktionsaufruf muss nicht beendet werden, um einen Abschluss zu erstellen. (2) Es ist keine Kopie der Werte der lokalen Variablen, sondern der Variablen selbst. (3) Es wird nicht angegeben, wer Zugriff auf diese Variablen hat.
dlaliberte
27
Beispiel 5 zeigt ein "Gotcha", bei dem der Code nicht wie beabsichtigt funktioniert. Aber es zeigt nicht, wie man es behebt. Diese andere Antwort zeigt einen Weg, dies zu tun.
Matt
190
Mir gefällt, wie dieser Beitrag mit großen, fett gedruckten Buchstaben beginnt, in denen "Closures Are Not Magic" steht, und das erste Beispiel mit "The Magic ist, dass eine Funktionsreferenz in JavaScript auch einen geheimen Verweis auf den Abschluss enthält, in dem sie erstellt wurde".
Andrew Macheret
6
Beispiel 3 ist das Mischen von Verschlüssen mit dem Heben von Javaskripten. Jetzt denke ich, dass es schwierig genug ist, nur Verschlüsse zu erklären, ohne das Hubverhalten zu beeinträchtigen. Dies hat mir am meisten geholfen: Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.von developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
caramba
3
ECMAScript 6 kann in diesem großartigen Artikel über das Schließen etwas ändern. Wenn Sie beispielsweise let i = 0anstelle von var i = 0Beispiel 5 verwenden, testList()wird das gedruckt, was Sie ursprünglich möchten.
Nier
3988

Jede Funktion in JavaScript unterhält eine Verknüpfung zu ihrer äußeren lexikalischen Umgebung. Eine lexikalische Umgebung ist eine Karte aller Namen (z. B. Variablen, Parameter) innerhalb eines Bereichs mit ihren Werten.

Wenn Sie also das functionSchlüsselwort sehen, hat Code innerhalb dieser Funktion Zugriff auf Variablen, die außerhalb der Funktion deklariert wurden.

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

Dies wird protokolliert, 16da die Funktion barüber dem Parameter xund der Variablen geschlossen wird tmp, die beide in der lexikalischen Umgebung der äußeren Funktion vorhanden sind foo.

Die Funktion barist zusammen mit ihrer Verbindung mit der lexikalischen Funktionsumgebung fooein Abschluss.

Eine Funktion muss nicht zurückkehren , um einen Abschluss zu erstellen. Einfach aufgrund seiner Deklaration schließt jede Funktion über ihrer einschließenden lexikalischen Umgebung und bildet einen Abschluss.

function foo(x) {
  var tmp = 3;

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

var bar = foo(2);
bar(10); // 16
bar(10); // 17

Die obige Funktion protokolliert auch 16, da der darin enthaltene Code barweiterhin auf Argumente xund Variablen verweisen kann tmp, obwohl sie nicht mehr direkt im Gültigkeitsbereich sind.

Da es jedoch tmpimmer noch im barVerschluss herumhängt, kann es inkrementiert werden. Sie wird bei jedem Anruf erhöht bar.

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

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 ecerstellt. Zusammen mit den Funktionsargumenten und dem Zielobjekt erhält dieser Ausführungskontext auch eine Verknüpfung zur lexikalischen Umgebung des aufrufenden Ausführungskontexts, dh, die in der äußeren lexikalischen Umgebung deklarierten Variablen (im obigen Beispiel sind sowohl aals auch b) verfügbar ec.

Jede Funktion erstellt einen Abschluss, da jede Funktion eine Verknüpfung zu ihrer äußeren lexikalischen Umgebung hat.

Beachten Sie, dass Variablen selbst innerhalb eines Abschlusses sichtbar sind und keine Kopien.

Ali
quelle
24
@feeela: Ja, jede JS-Funktion erstellt einen Abschluss. Variablen, auf die nicht verwiesen wird, werden wahrscheinlich für die Speicherbereinigung in modernen JS-Engines zugelassen. Dies ändert jedoch nichts an der Tatsache, dass dieser Kontext beim Erstellen eines Ausführungskontexts einen Verweis auf den einschließenden Ausführungskontext und seine Variablen und enthält Diese Funktion ist ein Objekt, das möglicherweise in einen anderen Variablenbereich verschoben werden kann, wobei diese ursprüngliche Referenz beibehalten wird. Das ist die Schließung.
@Ali Ich habe gerade entdeckt, dass die von mir bereitgestellte jsFiddle nichts beweist, da die deletefehlschlägt. Trotzdem wird die lexikalische Umgebung, die die Funktion als [[Scope]] mit sich herumträgt (und letztendlich beim Aufrufen als Basis für ihre eigene lexikalische Umgebung verwendet), bestimmt, wenn die Anweisung ausgeführt wird, die die Funktion definiert. Dies bedeutet , dass die Funktion wird über den gesamten Inhalt der Ausführung Umfang schließen, unabhängig davon , welche Werte sie bezieht sich eigentlich auf , und ob sie den Schutzbereich entweicht. Bitte schauen Sie sich die Abschnitte 13.2 und 10 in der Spezifikation an
Asad Saeeduddin
8
Dies war eine gute Antwort, bis versucht wurde, primitive Typen und Referenzen zu erklären. Es macht das völlig falsch und spricht davon, dass Literale kopiert werden, was wirklich nichts mit irgendetwas zu tun hat.
Ry-
12
Closures sind die Antwort von JavaScript auf klassenbasierte, objektorientierte Programmierung. JS ist nicht klassenbasiert, daher musste man einen anderen Weg finden, um einige Dinge zu implementieren, die sonst nicht implementiert werden könnten.
Bartłomiej Zalewski
2
Dies sollte die akzeptierte Antwort sein. Die Magie geschieht niemals in der inneren Funktion. Dies geschieht, wenn die äußere Funktion einer Variablen zugewiesen wird. Dadurch wird ein neuer Ausführungskontext für die innere Funktion erstellt, sodass die "private Variable" akkumuliert werden kann. Natürlich kann es, da die Variable, der die äußere Funktion zugewiesen ist, den Kontext beibehalten hat. Die erste Antwort macht das Ganze nur komplexer, ohne zu erklären, was dort wirklich passiert.
Albert Gao
2442

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 selbst wirklich nicht." Nun, ich habe versucht, einem 27-jährigen Freund JS-Schließungen zu erklären, und bin völlig gescheitert.

Kann jemand davon ausgehen, dass ich 6 Jahre alt bin und mich seltsamerweise für dieses Thema interessiere?

Ich bin mir ziemlich sicher, dass ich einer der wenigen war, die versucht haben, die ursprüngliche Frage wörtlich zu nehmen. Seitdem ist die Frage mehrmals mutiert, sodass meine Antwort jetzt unglaublich albern und fehl am Platz erscheint. Hoffentlich macht die allgemeine Idee der Geschichte einigen Spaß.


Ich bin ein großer Fan von Analogie und Metapher, wenn ich schwierige Konzepte erkläre. Lassen Sie mich also eine Geschichte ausprobieren.

Es war einmal:

Da war eine Prinzessin ...

function princess() {

Sie lebte in einer wundervollen Welt voller Abenteuer. Sie traf ihren Prinzen Charming, ritt mit einem Einhorn um ihre Welt, kämpfte gegen Drachen, begegnete sprechenden Tieren und vielen anderen fantastischen Dingen.

    var adventures = [];

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

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

    /* ... */

Aber sie würde immer wieder in ihre langweilige Welt der Hausarbeit und Erwachsenen zurückkehren müssen.

    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 die Einhörner oder Drachen glauben, weil sie sie niemals sehen konnten. Die Erwachsenen sagten, dass sie nur in der Fantasie des kleinen Mädchens existierten.

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

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

Jacob Swartwood
quelle
340
Ich liebe diese Erklärung wirklich. Für diejenigen, die es lesen und nicht folgen, lautet die Analogie: Die Funktion princess () ist ein komplexer Bereich, der private Daten enthält. Außerhalb der Funktion können die privaten Daten nicht gesehen oder abgerufen werden. Die Prinzessin behält die Einhörner, Drachen, Abenteuer usw. in ihrer Vorstellung (private Daten) und die Erwachsenen können sie nicht für sich selbst sehen. ABER die Vorstellungskraft der Prinzessin wird im Abschluss der story()Funktion festgehalten , die die einzige Schnittstelle darstellt, die die littleGirlInstanz in die Welt der Magie entlarvt.
Patrick M
Also hier storyist die Schließung, aber wäre der Code var story = function() {}; return story;dann littleGirldie Schließung gewesen. Zumindest habe ich den Eindruck, dass MDN 'private' Methoden mit Closures verwendet : "Diese drei öffentlichen Funktionen sind Closures, die dieselbe Umgebung verwenden."
icc97
16
@ icc97, yes, storyist ein Abschluss, der auf die im Rahmen von bereitgestellte Umgebung verweist princess. princessist auch ein weiterer impliziter Abschluss, dh das princessund das littleGirlwürde jeden Verweis auf ein parentsArray teilen , das in der Umgebung / dem Bereich littleGirlexistieren würde, in dem das existiert und das princessdefiniert ist.
Jacob Swartwood
6
@BenjaminKrupp Ich habe einen expliziten Codekommentar hinzugefügt, um zu zeigen / zu implizieren, dass es mehr Operationen im Körper gibt princessals geschrieben. Leider ist diese Geschichte in diesem Thread jetzt etwas fehl am Platz. Ursprünglich lautete die Frage: "Erklären Sie einem 5-Jährigen JavaScript-Schließungen". Meine Antwort war die einzige, die das überhaupt versuchte. Ich bezweifle nicht, dass es kläglich gescheitert wäre, aber zumindest hätte diese Antwort die Chance gehabt, das Interesse eines Fünfjährigen zu wecken.
Jacob Swartwood
11
Eigentlich machte das für mich vollkommen Sinn. Und ich muss zugeben, dass ich mich irgendwie komisch fühle, wenn ich endlich einen JS-Abschluss verstehe, indem ich Geschichten über Prinzessinnen und Abenteuer verwende.
Kristallisieren Sie den
753

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

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

Ihr Kind kann den Anweisungen in zwei Schritten folgen. Wenn Sie beispielsweise zu Ihrem Kind sagen: "Gehen Sie in die Küche und holen Sie mir einen Müllsack", können sie sich an diese Richtung erinnern.

Wir können dieses Beispiel verwenden, um Verschlüsse wie folgt zu erklären:

Die Küche ist ein Verschluss, der eine lokale Variable hat, genannt trashBags. In der Küche gibt es eine Funktion, die getTrashBageinen Müllsack abruft und 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:

  • Jedes Mal makeKitchen(), wenn aufgerufen wird, wird ein neuer Abschluss mit einem eigenen separaten erstellttrashBags .
  • Die trashBagsVariable befindet sich lokal im Inneren jeder Küche und ist nicht außerhalb zugänglich, sondern die innere Funktion auf dergetTrashBag Grundstück hat Zugriff darauf.
  • Jeder Funktionsaufruf erzeugt einen Verschluss, aber es wäre nicht erforderlich, den Verschluss beizubehalten, es sei denn, eine innere Funktion, die Zugriff auf das Innere des Verschlusses hat, kann von außerhalb des Verschlusses aufgerufen werden. Wenn Sie das Objekt mit der getTrashBagFunktion zurückgeben, geschieht dies hier.
dlaliberte
quelle
6
Eigentlich verwechselbar, der makeKitchen Funktion Aufruf ist der eigentliche Verschluss, nicht die Küche Objekt , dass er zurückkehrt.
dlaliberte
6
Nachdem ich mich durch die anderen gekümmert hatte, fand ich diese Antwort als die einfachste Möglichkeit zu erklären, was und warum die Verschlüsse sind.
Chetabahana
3
Zu viel Menü und Vorspeise, zu wenig Fleisch und Kartoffeln. Sie könnten diese Antwort mit nur einem kurzen Satz verbessern: "Ein Abschluss ist der versiegelte Kontext einer Funktion, da kein von Klassen bereitgestellter Scoping-Mechanismus vorhanden ist."
Staplerfahrer
584

Der Strohmann

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

Ziemlich offensichtliche Lösung

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

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

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

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

Dies wird nun funktionieren, greift jedoch in den äußeren Bereich ein, indem eine Variable hinzugefügt wird, deren einziger Zweck darin besteht, die Anzahl zu verfolgen. In einigen Situationen ist dies vorzuziehen, da Ihre äußere Anwendung möglicherweise Zugriff auf diese Informationen benötigt. In diesem Fall ändern wir jedoch nur das Verhalten jedes dritten Klicks. Daher ist es vorzuziehen, diese Funktionalität in den Ereignishandler 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 einige Dinge.

Im obigen Beispiel verwende ich das Schließverhalten von JavaScript. Durch dieses Verhalten kann jede Funktion auf unbestimmte Zeit 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 Zählvariable hat (aufgrund des oben erläuterten Schließverhaltens), ergibt sich ein privater Bereich für die Verwendung durch das Ergebnis Funktion ... Nicht so einfach? Lassen Sie es uns 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 stehen der zurückgegebenen Funktion zur Verfügung, stehen dem zurückgegebenen Funktionsobjekt jedoch nicht direkt zur Verfügung ...

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

Kapiert? In unserem primären Beispiel ist die Zählvariable im Abschluss enthalten und steht dem Ereignishandler immer zur Verfügung, sodass der Status von Klick zu Klick beibehalten wird.

Auf diesen Status der privaten Variablen kann sowohl für das Lesen als auch für die Zuweisung zu den Variablen mit privatem Bereich vollständig zugegriffen werden.

Los geht's; Sie kapseln dieses Verhalten jetzt vollständig ein.

Vollständiger Blog-Beitrag (einschließlich Überlegungen zu jQuery)

jondavidjohn
quelle
11
Ich stimme Ihrer Definition eines Abschlusses nicht zu. Es gibt keinen Grund, warum es sich selbst aufrufen muss. Es ist auch ein bisschen simpel (und ungenau) zu sagen, dass es "zurückgegeben" werden muss (viele Diskussionen darüber in den Kommentaren der Top-Antwort auf diese Frage)
James Montagne
40
@ James, auch wenn Sie nicht einverstanden sind, sein Beispiel (und der gesamte Beitrag) ist eines der besten, die ich je gesehen habe. Die Frage ist zwar nicht alt und für mich gelöst, verdient aber eine +1.
E-Satis
84
"Ich muss wissen, wie oft auf eine Schaltfläche geklickt wurde, und bei jedem dritten Klick etwas tun ..." Dies erregte meine Aufmerksamkeit. Ein Anwendungsfall und die Lösung, die zeigt, dass eine Schließung keine so mysteriöse Sache ist und dass viele von uns sie geschrieben haben, aber den offiziellen Namen nicht genau kannten.
Chris22
Schönes Beispiel, weil es zeigt, dass "count" im 2. Beispiel den Wert von "count" beibehält und nicht jedes Mal auf 0 zurückgesetzt wird, wenn auf das "Element" geklickt wird. Sehr informativ!
Adam
+1 für das Schließverhalten . Können wir das Schließverhalten auf Funktionen in Javascript beschränken oder kann dieses Konzept auch auf andere Strukturen der Sprache angewendet werden?
Dziamid
492

Abschlüsse sind schwer zu erklären, da sie dazu dienen, ein Verhalten zum Funktionieren zu bringen, von dem jeder intuitiv erwartet, dass es funktioniert. Ich finde den besten Weg, sie zu erklären (und wie ich gelernt habe, was sie tun), mir 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 kennen würde? Ersetzen Sie einfach den Aufruf in der letzten Zeile durch seinen Methodenkörper (was im Grunde das ist, was Funktionsaufrufe tun) und Sie erhalten:

console.log(x + 3);

Wo ist die Definition von x? Wir haben es im aktuellen Bereich nicht definiert. Die einzige Lösung besteht darin, den Geltungsbereich (oder besser gesagt den Geltungsbereich der Eltern) plus5 herumtragen zu lassen . Auf diese Weise xist es genau definiert und an den Wert 5 gebunden.

Konrad Rudolph
quelle
11
Dies ist genau die Art von Beispiel, die viele Menschen in die Irre führt und denkt, dass es die Werte sind, die in der zurückgegebenen Funktion verwendet werden, nicht die veränderbare Variable selbst. Wenn es in "return x + = y" oder besser noch in diese und eine andere Funktion "x * = y" geändert würde, wäre klar, dass nichts kopiert wird. Stellen Sie sich vor, Sie verwenden stattdessen Heap-Frames, die nach der Rückkehr der Funktion weiterhin vorhanden sein können.
Matt
14
@ Matt Ich bin anderer Meinung. Ein Beispiel soll nicht alle Eigenschaften vollständig dokumentieren. Es soll reduzierend sein und das herausragende Merkmal eines Konzepts veranschaulichen. Das OP bat um eine einfache Erklärung („für einen Sechsjährigen“). Nehmen Sie die akzeptierte Antwort: Es kann überhaupt keine präzise Erklärung liefern, gerade weil es versucht, erschöpfend zu sein. (Ich stimme Ihnen zu, dass es eine wichtige Eigenschaft von JavaScript ist, dass die Bindung eher durch Referenz als durch Wert erfolgt. Aber auch hier ist eine erfolgreiche Erklärung eine, die sich auf das Nötigste reduziert.)
Konrad Rudolph
@KonradRudolph Ich mag den Stil und die Kürze Ihres Beispiels. Ich empfehle einfach, es leicht zu ändern, damit der letzte Teil "Die einzige Lösung ist ..." wahr wird. Derzeit gibt es in der Tat eines andere, einfachere Lösung für Ihr Szenario, das sich nicht an Javascript Fortsetzungen entspricht, und tut entspricht ein weit verbreitetes Missverständnis über das, was Fortsetzungen sind. Daher ist das Beispiel in seiner jetzigen Form gefährlich. Dies hat nicht mit der erschöpfenden Auflistung von Eigenschaften zu tun, sondern mit dem Verständnis, was das x in der zurückgegebenen Funktion ist, die schließlich der Hauptpunkt ist.
Matt
@ Matt Hmm, ich bin nicht sicher, ob ich dich vollständig verstehe, aber ich beginne zu sehen, dass du vielleicht einen gültigen Punkt hast. Könnten Sie vielleicht erklären, was Sie in einem Kern / Pastetchen oder in einem Chatroom meinen, da die Kommentare zu kurz sind? Vielen Dank.
Konrad Rudolph
2
@KonradRudolph Ich glaube, ich war mir über den Zweck des x + = y nicht klar. Der Zweck war nur zu zeigen, dass wiederholte Aufrufe der zurückgegebenen Funktion weiterhin dieselbe Variable x verwenden (im Gegensatz zu demselben Wert , von dem die Leute glauben, dass er beim Erstellen der Funktion "eingefügt" wird). Dies ist wie die ersten beiden Warnungen in Ihrer Geige. Der Zweck einer zusätzlichen Funktion x * = y wäre zu zeigen, dass mehrere zurückgegebene Funktionen alle dasselbe x haben.
Matt
376

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

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

So kann ich meine Flugzeuggeschichte in den Code konvertieren.

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

Max Tkachenko
quelle
26
Gut gespielt und beantwortet das Originalplakat. Ich denke, das ist die beste Antwort. Ich wollte das Gepäck auf ähnliche Weise benutzen: Stellen Sie sich vor, Sie gehen zu Omas Haus und packen Ihren Nintendo DS-Koffer mit Spielkarten in Ihren Koffer, packen dann aber den Koffer in Ihren Rucksack und stecken auch Spielkarten in Ihre Rucksacktaschen DANN steckst du das Ganze in einen großen Koffer mit mehr Spielkarten in die Taschen des Koffers. Wenn Sie zu Omas Haus kommen, können Sie jedes Spiel auf Ihrem DS spielen, solange alle äußeren Fälle offen sind. oder etwas in diesem Sinne.
Slartibartfast
376

TLDR

Ein Abschluss ist eine Verbindung zwischen einer Funktion und ihrer äußeren lexikalischen Umgebung (dh wie geschrieben), sodass die in dieser Umgebung definierten Bezeichner (Variablen, Parameter, Funktionsdeklarationen usw.) innerhalb der Funktion sichtbar sind, unabhängig davon, wann oder von wo die Funktion aufgerufen wird.

Einzelheiten

In der Terminologie der ECMAScript-Spezifikation kann gesagt werden, dass ein Abschluss durch die [[Environment]]Referenz jedes Funktionsobjekts implementiert wird , was auf die lexikalische Umgebung verweist, in der die Funktion definiert ist.

Wenn eine Funktion über die interne [[Call]]Methode aufgerufen wird, wird die [[Environment]]Referenz auf das Funktionsobjekt in die äußere Umgebungsreferenz des Umgebungsdatensatzes des neu erstellten Ausführungskontexts (Stapelrahmen) kopiert .

Im folgenden Beispiel wird die Funktion füber der lexikalischen Umgebung des globalen Ausführungskontexts geschlossen:

function f() {}

Im folgenden Beispiel wird die Funktion hüber der lexikalischen Funktionsumgebung geschlossen g, die wiederum über der lexikalischen Umgebung des globalen Ausführungskontexts geschlossen wird.

function g() {
    function h() {}
}

Wenn eine innere Funktion von einer äußeren zurückgegeben wird, bleibt die äußere lexikalische Umgebung bestehen, nachdem die äußere Funktion zurückgekehrt ist. Dies liegt daran, dass die äußere lexikalische Umgebung verfügbar sein muss, wenn die innere Funktion schließlich aufgerufen wird.

Im folgenden Beispiel wird die Funktion jüber der lexikalischen Umgebung der Funktion geschlossen. Dies ibedeutet, dass die Variable xvon der inneren Funktion aus sichtbar ist j, lange nachdem die Funktion idie Ausführung abgeschlossen hat:

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

In einem Abschluss sind die Variablen in der äußeren lexikalischen Umgebung selbst verfügbar, keine Kopien.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

Die Kette lexikalischer Umgebungen, die über Verweise auf die äußere Umgebung zwischen Ausführungskontexten verbunden ist, bildet a Bereichskette und definiert die Bezeichner, die für eine bestimmte Funktion sichtbar sind.

Bitte beachten Sie, dass diese Antwort in dem Versuch, die Klarheit und Genauigkeit zu verbessern, gegenüber dem Original erheblich geändert wurde.

Ben
quelle
56
Wow, ich wusste nie, dass man so einen String-Ersatz verwenden kann console.log. Wenn jemand anderes interessiert ist, gibt es mehr: developer.mozilla.org/en-US/docs/DOM/…
Flash
7
Variablen, die in der Parameterliste der Funktion enthalten sind, sind ebenfalls Teil des Abschlusses (z. B. nicht nur beschränkt auf var).
Thomas Eding
Verschlüsse klingen eher nach Objekten und Klassen usw. Ich bin mir nicht sicher, warum nicht viele Leute diese beiden vergleichen - es wäre für uns Anfänger einfacher zu lernen!
Almaruf
365

Dies ist ein Versuch, einige (mögliche) Missverständnisse über Schließungen auszuräumen, die in einigen der anderen Antworten vorkommen.

  • Ein Abschluss wird nicht nur erstellt, wenn Sie eine innere Funktion zurückgeben. Tatsächlich muss die umschließende Funktion überhaupt nicht zurückkehren , damit ihr Abschluss erstellt wird. Sie können stattdessen Ihre innere Funktion einer Variablen in einem äußeren Bereich zuweisen oder sie als Argument an eine andere Funktion übergeben, wo sie sofort oder zu einem späteren Zeitpunkt aufgerufen werden kann. Daher wird wahrscheinlich das Schließen der umschließenden Funktion erzeugt , sobald die umschließende Funktion aufgerufen wird, da jede innere Funktion Zugriff auf dieses Schließen hat, wenn die innere Funktion aufgerufen wird, bevor oder nachdem die umschließende Funktion zurückkehrt.
  • Ein Abschluss verweist nicht auf eine Kopie der alten Werte von Variablen in seinem Bereich.Die Variablen selbst sind Teil des Abschlusses. Daher ist der Wert, der beim Zugriff auf eine dieser Variablen angezeigt wird, der neueste Wert zum Zeitpunkt des Zugriffs. Aus diesem Grund können innere Funktionen, die innerhalb von Schleifen erstellt werden, schwierig sein, da jede auf dieselben äußeren Variablen zugreifen kann, anstatt zum Zeitpunkt der Erstellung oder des Aufrufs der Funktion eine Kopie der Variablen abzurufen.
  • Die "Variablen" in einem Abschluss enthalten alle benannten Funktionen , die innerhalb der Funktion deklariert sind. Sie enthalten auch Argumente der Funktion. Ein Abschluss hat auch Zugriff auf die Variablen seines enthaltenen Abschlusses bis hin zum globalen Bereich.
  • Closures verwenden Speicher, verursachen jedoch keine Speicherverluste, da JavaScript selbst seine eigenen kreisförmigen Strukturen bereinigt, auf die nicht verwiesen wird. Internet Explorer-Speicherlecks, die Schließungen betreffen, werden erstellt, wenn DOM-Attributwerte, die auf Schließungen verweisen, nicht getrennt werden können, wodurch Verweise auf möglicherweise kreisförmige Strukturen beibehalten werden.
dlaliberte
quelle
15
James, ich sagte, der Abschluss wird "wahrscheinlich" zum Zeitpunkt des Aufrufs der einschließenden Funktion erstellt, da es plausibel ist, dass eine Implementierung die Erstellung eines Abschlusses bis zu einem späteren Zeitpunkt verschieben könnte, wenn entschieden wird, dass ein Abschluss unbedingt erforderlich ist. Wenn in der umschließenden Funktion keine innere Funktion definiert ist, ist kein Verschluss erforderlich. Vielleicht könnte es also warten, bis die erste innere Funktion erstellt wird, um dann einen Abschluss aus dem Aufrufkontext der einschließenden Funktion zu erstellen.
dlaliberte
9
@ Beetroot-Beetroot Angenommen, wir haben eine innere Funktion, die an eine andere Funktion übergeben wird, wo sie verwendet wird, bevor die äußere Funktion zurückkehrt, und nehmen wir an, dass wir dieselbe innere Funktion auch von der äußeren Funktion zurückgeben. Es ist in beiden Fällen identisch dieselbe Funktion, aber Sie sagen, dass die innere Funktion vor der Rückkehr der äußeren Funktion an den Aufrufstapel "gebunden" ist, während die innere Funktion nach ihrer Rückkehr plötzlich an einen Abschluss gebunden ist. Es verhält sich in beiden Fällen identisch; Die Semantik ist identisch. Sprechen Sie also nicht nur über Implementierungsdetails?
dlaliberte
7
@ Beetroot-Beetroot, danke für dein Feedback und ich bin froh, dass ich dich zum Nachdenken gebracht habe. Ich sehe immer noch keinen semantischen Unterschied zwischen dem Live-Kontext der äußeren Funktion und demselben Kontext, wenn er bei der Rückkehr der Funktion geschlossen wird (wenn ich Ihre Definition verstehe). Die innere Funktion ist egal. Die Garbage Collection ist egal, da die innere Funktion in beiden Fällen einen Verweis auf den Kontext / Abschluss beibehält und der Aufrufer der äußeren Funktion nur den Verweis auf den Aufrufkontext löscht. Aber es ist für die Leute verwirrend und vielleicht besser, es einfach einen Anrufkontext zu nennen.
dlaliberte
9
Dieser Artikel ist schwer zu lesen, aber ich denke, er unterstützt tatsächlich das, was ich sage. Es heißt: "Ein Abschluss wird gebildet, indem ein [...] Funktionsobjekt zurückgegeben wird oder indem beispielsweise einer globalen Variablen ein Verweis auf ein solches Funktionsobjekt direkt zugewiesen wird." Ich meine nicht, dass GC irrelevant ist. Aufgrund von GC und weil die innere Funktion an den Aufrufkontext der äußeren Funktion (oder [[scope]], wie im Artikel angegeben) angehängt ist, spielt es keine Rolle, ob der äußere Funktionsaufruf zurückgegeben wird, weil diese Bindung mit der inneren Funktion ist das Wichtigste.
dlaliberte
3
Gute Antwort! Eine Sache, die Sie hinzufügen sollten, ist, dass alle Funktionen über den gesamten Inhalt des ausführenden Bereichs geschlossen werden, in dem sie definiert sind. Es spielt keine Rolle, ob sie auf einige oder keine der Variablen aus dem übergeordneten Bereich verweisen: Ein Verweis auf die lexikalische Umgebung des übergeordneten Bereichs wird bedingungslos als [[Bereich]] gespeichert. Dies ist aus dem Abschnitt zur Funktionserstellung in der ECMA-Spezifikation ersichtlich.
Asad Saeeduddin
236

Ich habe vor einiger Zeit einen Blog-Beitrag geschrieben, in dem Schließungen erklärt wurden. Hier ist, was ich über Schließungen gesagt habe, um herauszufinden, warum Sie eine wollen.

Closures sind eine Möglichkeit, einer Funktion persistente, private Variablen zuzuweisen, dh Variablen , die nur einer Funktion bekannt sind, und die Informationen aus früheren Ausführungszeiten verfolgen können.

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

Vollständiger Beitrag:

Also, was sind diese Verschlusssachen?

Nathan Long
quelle
Könnte der Hauptvorteil von Schließungen anhand dieses Beispiels hervorgehoben werden? Angenommen, ich habe eine Funktion emailError (sendToAddress, errorString). Ich könnte dann sagen devError = emailError("[email protected]", errorString)und dann meine eigene benutzerdefinierte Version einer freigegebenen emailError-Funktion haben.
Devin G Rhode
Diese Erklärung und das dazugehörige perfekte Beispiel im Link zu (Verschlusssachen) ist der beste Weg, Verschlüsse zu verstehen und sollte ganz oben stehen!
HopeKing
215

Verschlüsse sind einfach:

Das folgende einfache Beispiel behandelt alle Hauptpunkte von JavaScript-Schließungen. * *  

Hier ist eine Fabrik, die Taschenrechner herstellt, die addieren 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 entscheidende Punkt: Jeder Aufruf von 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 Stapelrahmen vertraut sind, scheint dieser Rechner seltsam: Wie können sie halten den Zugriff nnachmake_calculator Rückgabe ? Die Antwort ist, sich vorzustellen, dass JavaScript keine "Stapelrahmen" verwendet, sondern "Heap-Rahmen", die nach dem Funktionsaufruf, der sie zurückgegeben hat, bestehen bleiben können.

Innere Funktionen wie addundmultiply , die auf Variablen zugreifen, die in einer äußeren Funktion ** deklariert sind, werden als Closures bezeichnet .

Das ist so ziemlich alles, was es zu Schließungen gibt.



* Zum Beispiel werden alle Punkte im Artikel "Closures for Dummies" behandelt, die in einer anderen Antwort angegeben sind , mit Ausnahme von Beispiel 6, in dem lediglich gezeigt wird, dass Variablen verwendet werden können, bevor sie deklariert werden. Dies ist eine nette Tatsache, aber völlig unabhängig von Closures. Es werden auch alle Punkte in der akzeptierten Antwort behandelt , mit Ausnahme der Punkte (1), bei denen Funktionen ihre Argumente in lokale Variablen kopieren (die benannten Funktionsargumente), und (2) beim Kopieren von Zahlen wird eine neue Zahl erstellt, beim Kopieren jedoch eine Objektreferenz gibt Ihnen einen weiteren Verweis auf dasselbe Objekt. Diese sind ebenfalls gut zu wissen, aber auch hier völlig unabhängig von Schließungen. Es ist auch dem Beispiel in sehr ähnlich dieser Antwort aber etwas kürzer und weniger abstrakt. Es geht nicht um den Punkt vonDiese Antwort oder dieser Kommentar besagt Strom, was bedeutet, dass JavaScript es schwierig macht, das anzuschließenWert einer Schleifenvariablen in Ihre innere Funktion: Der Schritt "Einstecken" kann nur mit einer Hilfsfunktion ausgeführt werden, die Ihre innere Funktion einschließt und bei jeder Schleifeniteration aufgerufen wird. (Genau genommen greift die innere Funktion auf die Kopie der Variablen der Hilfsfunktion zu, anstatt dass etwas angeschlossen ist.) Auch dies ist sehr nützlich, wenn Sie Verschlüsse erstellen, aber nicht Teil dessen, was ein Verschluss ist oder wie er funktioniert. Es gibt zusätzliche Verwirrung aufgrund von Verschlüssen, die in funktionalen Sprachen wie ML unterschiedlich funktionieren, wobei Variablen eher an Werte als an Speicherplatz gebunden sind und einen konstanten Strom von Personen bereitstellen, die Verschlüsse auf eine Art und Weise verstehen (nämlich das "Einstecken") Einfach falsch für JavaScript, wo Variablen immer an Speicherplatz und niemals an Werte gebunden sind.

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

Matt
quelle
Was würde passieren, wenn Sie Folgendes aufrufen würden: second_calculator = first_calculator (); anstelle von second_calculator = make_calculator (); ? Sollte das gleiche sein, oder?
Ronen Festinger
4
@Ronen: Da first_calculatores sich um ein Objekt (keine Funktion) handelt, sollten Sie keine Klammern verwenden second_calculator = first_calculator;, da es sich um eine Zuweisung und nicht um einen Funktionsaufruf handelt. Um Ihre Frage zu beantworten, würde es nur einen Aufruf von make_calculator geben, sodass nur ein Rechner ausgeführt würde, und die Variablen first_calculator und second_calculator würden sich beide auf denselben Rechner beziehen, sodass die Antworten 3, 403, 4433, 44330 wären.
Matt
204

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

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

Magne
quelle
200

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

Ich denke immer noch, dass Googles Erklärung sehr gut funktioniert und prägnant 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);​

Beweisen Sie, dass dieses Beispiel einen Abschluss erzeugt, auch wenn die innere Funktion nicht zurückkehrt

* AC # Frage

Chris S.
quelle
11
Der Code ist als Beispiel für einen Abschluss "korrekt", obwohl er den Teil des Kommentars zur Verwendung des Abschlusses nach der Rückkehr der OuterFunction nicht anspricht. Es ist also kein gutes Beispiel. Es gibt viele andere Möglichkeiten, wie ein Verschluss verwendet werden kann, bei denen die innerFunction nicht zurückgegeben wird. zB könnte innerFunction an eine andere Funktion übergeben werden, wo es sofort aufgerufen oder gespeichert und einige Zeit später aufgerufen wird. In allen Fällen hat es Zugriff auf den OuterFunction-Kontext, der beim Aufruf erstellt wurde.
dlaliberte
6
@syockit Nein, Moss ist falsch. Ein Abschluss wird erstellt, unabhängig davon, ob die Funktion jemals dem Bereich entgeht, in dem sie definiert ist, und ein bedingungslos erstellter Verweis auf die lexikalische Umgebung des übergeordneten Bereichs macht alle Variablen im übergeordneten Bereich für alle Funktionen verfügbar, unabhängig davon, ob sie außerhalb oder innerhalb aufgerufen werden den Umfang, in dem sie erstellt wurden.
Asad Saeeduddin
176

Ich neige dazu, durch gute / schlechte Vergleiche besser zu lernen. Ich mag es, Arbeitscode gefolgt von nicht funktionierendem Code zu sehen, auf den wahrscheinlich jemand stößt. Ich habe eine jsFiddle zusammengestellt , die 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]());
}
  • Im obigen Code createClosure(n)wird in jeder Iteration der Schleife aufgerufen. Beachten Sie, dass ich die Variable benannt habe, num hervorzuheben, dass es sich um eine neue handelt Variable handelt, die in einem neuen Funktionsbereich erstellt wurde und nicht dieselbe Variable indexist, die an den äußeren Bereich gebunden ist.

  • Dies schafft einen neuen Bereich und nist an diesen Bereich gebunden. Dies bedeutet, dass wir 10 separate Bereiche haben, einen für jede Iteration.

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

  • Innerhalb jedes Bereichs nist an den Wert gebunden, den es beim createClosure(n)Aufrufen hatte, sodass die verschachtelte Funktion, die zurückgegeben wird, immer den Wert zurückgibt n, den sie beim Zeitpunkt hattecreateClosure(n) Aufrufen hatte.

Verschlü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 verschoben createClosureArray() Funktion und die Funktion gibt jetzt nur das fertige Array zurück, was auf den ersten Blick intuitiver erscheint.

  • Was vielleicht nicht offensichtlich ist, ist das seitdem createClosureArray() nur aufgerufen wird, nur ein Bereich für diese Funktion erstellt wird, anstatt einer 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 zurückkehrenden Array Funktionen hinzu index. Beachten Sie, dass indexinnerhalb der definiert istcreateClosureArray Funktion die immer nur einmal aufgerufen wird.

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

  • Alle dem Array hinzugefügten Funktionen geben die indexVariable SAME aus dem übergeordneten Bereich zurück, in dem sie definiert wurde, anstatt 10 verschiedene 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 indexgeändert wurde, war der Endwert 10, daher gibt jede dem Array hinzugefügte Funktion den Wert der einzelnen indexVariablen zurück, die jetzt auf 10 gesetzt ist.

Ergebnis

SCHLIESSUNGEN RICHTIG GEMACHT
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

FALSCHE SCHLIESSUNGEN
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

Chev
quelle
1
Schöne Ergänzung, danke. Um es klarer zu machen, kann man sich vorstellen, wie das "schlechte" Array in der "schlechten" Schleife bei jeder Iteration erstellt wird: 1. Iteration: [function () {return 'n =' + 0;}] 2. Iteration: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3. Iteration: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] usw. Jedes Mal, wenn sich der Indexwert ändert, wird er in allen Funktionen wiedergegeben bereits zum Array hinzugefügt.
Alex Alexeev
3
Mit letfor wird varder Unterschied behoben.
Rupam Datta
Ist "Schließen richtig gemacht" hier nicht ein Beispiel für "Schließen innerhalb des Verschlusses"?
TechnicalSmile
Ich meine, jede Funktion ist technisch gesehen ein Abschluss, aber der wichtige Teil ist, dass die Funktion eine neue Variable innerhalb definiert. Die Funktion, die abgerufen wird, gibt nur Referenzen zurück, ndie in einem neuen Abschluss erstellt wurden. Wir geben nur eine Funktion zurück, damit wir sie im Array speichern und später aufrufen können.
Chev
Wenn Sie das Ergebnis nur in der ersten Iteration im Array speichern möchten, können Sie es wie folgt einbinden : arr[index] = (function (n) { return 'n = ' + n; })(index);. Aber dann speichern Sie die resultierende Zeichenfolge im Array und nicht eine aufzurufende Funktion, die den Punkt meines Beispiels zunichte macht.
Chev
164

Wikipedia zu Schließungen :

In der Informatik ist ein Abschluss eine Funktion zusammen mit einer Referenzierungsumgebung für die nichtlokalen Namen (freien Variablen) dieser Funktion.

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

Da die bereichsdefinierende Konstruktion in JavaScript eine Funktion ist , kein Codeblock wie in vielen anderen Sprachen, meinen wir unter Schließen in JavaScript normalerweise eine Funktion, die mit nichtlokalen Variablen arbeitet, die in bereits ausgeführten Umgebungsfunktionen definiert sind .

Verschlüsse werden häufig zum Erstellen von Funktionen mit versteckten 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

Im obigen Beispiel wird eine anonyme Funktion verwendet, die einmal ausgeführt wurde. Das muss aber nicht sein. Es 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 weiteres Anwendungsbeispiel für Schließungen ist, wenn wir keine Funktion zurückgeben, sondern ein Objekt, das mehrere Funktionen für verschiedene Zwecke enthält, wobei jede dieser Funktionen Zugriff auf dieselben Daten hat.

Mykhal
quelle
2
Dies ist die beste Erklärung für JavaScript-Schließungen. Sollte die gewählte Antwort sein. Der Rest ist unterhaltsam genug, aber dieser ist tatsächlich praktisch für echte JavaScript-Codierer nützlich.
Geoidesic
136

Ich habe ein interaktives JavaScript-Tutorial zusammengestellt, um zu erklären, wie Verschlüsse 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
Nathan Whitehead
quelle
128

Die Kinder werden sich immer an die Geheimnisse erinnern, die sie mit ihren Eltern geteilt haben, auch wenn ihre Eltern weg sind. Dies sind Verschlüsse für Funktionen.

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

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

Jedes Mal, wenn Sie es aufrufen, wird die lokale Variable "name" erstellt und mit "Mary" versehen. Und jedes Mal, wenn die Funktion beendet wird, geht die Variable verloren und der Name wird vergessen.

Wie Sie vielleicht erraten haben, muss es einen geheimen Ort geben, an dem sie gespeichert werden, da die Variablen bei jedem Aufruf der Funktion neu erstellt werden und niemand anderes sie kennt. Es könnte als Kammer der Geheimnisse oder Stapel oder lokaler Bereich 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 sie 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 vom geheimen Ort 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 rennt", geht 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 "Alice" nennen, wird es antworten

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

Das ist alles was es zu erzählen gibt.

Tero Tolonen
quelle
15
Dies ist die Erklärung, die für mich am sinnvollsten war, da sie keine wesentlichen Vorkenntnisse der Fachbegriffe voraussetzt. Die am häufigsten gewählte Erklärung geht davon aus, dass die Person, die Abschlüsse nicht versteht, Begriffe wie "lexikalischer Umfang" und "Ausführungskontext" vollständig versteht - obwohl ich diese konzeptionell verstehen kann, glaube ich nicht, dass ich so bin Ich fühle mich mit den Details wohl, wie ich sein sollte, und die Erklärung ohne Jargon ist es, die dazu geführt hat, dass Verschlüsse endlich für mich klicken, danke. Als Bonus denke ich, dass es auch erklärt, welcher Umfang sehr präzise ist.
Emma W
103

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 das wahrscheinlich mehrmals am Tag.


Es gibt keinen Grund zu der Annahme, dass Verschlüsse ein komplexer Design-Hack sind, um bestimmte Probleme anzugehen. Nein, bei Closures geht es nur darum, eine Variable zu verwenden, die aus der Perspektive, in der die Funktion deklariert (nicht ausgeführt) wurde , aus einem höheren Bereich stammt .

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

Floribon
quelle
5
Diese Antwort scheint nicht zu helfen, Menschen zu verwirren. Ein grobes Äquivalent in einer traditionellen Programmiersprache könnte darin bestehen, b () als Methode für ein Objekt zu erstellen, das auch eine private Konstante oder Eigenschaft hat a. Meiner Meinung nach ist die Überraschung, dass das JS-Bereichsobjekt effektiv aals Eigenschaft und nicht als Konstante bereitgestellt wird. Und Sie werden dieses wichtige Verhalten nur bemerken, wenn Sie es ändern, wie inreturn a++;
Jon Coombs
1
Genau das, was Jon gesagt hat. Bevor ich endlich Verschlüsse machte, fiel es mir schwer, praktische Beispiele zu finden. Ja, Floribon hat eine Schließung geschaffen, aber für mich ohne Ausbildung hätte mich das absolut nichts gelehrt.
Chev
3
Dies definiert nicht, was ein Verschluss ist - es ist lediglich ein Beispiel, das einen verwendet. Und es geht nicht um die Nuance dessen, was passiert, wenn der Umfang endet; Ich glaube nicht, dass irgendjemand eine Frage zum lexikalischen Scoping hatte, wenn alle Bereiche noch vorhanden sind, insbesondere im Fall einer globalen Variablen.
Gerard ONeill
91

Beispiel für den ersten Punkt von dlaliberte:

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
someisaac
quelle
Kleine Klarstellung über eine mögliche Mehrdeutigkeit. Als ich sagte "Tatsächlich muss die umschließende Funktion überhaupt nicht zurückkehren." Ich meinte nicht "keinen Wert zurückgeben", sondern "immer noch aktiv". Das Beispiel zeigt diesen Aspekt also nicht, obwohl es eine andere Möglichkeit zeigt, wie die innere Funktion an den äußeren Bereich übergeben werden kann. Der Hauptpunkt, den ich ansprechen wollte, betrifft den Zeitpunkt der Erstellung des Verschlusses (für die umschließende Funktion), da einige Leute zu glauben scheinen, dass dies passiert, wenn die umschließende Funktion zurückkehrt. Ein anderes Beispiel ist erforderlich, um zu zeigen, dass der Abschluss beim Aufruf einer Funktion erstellt wird .
dlaliberte
88

Bei einem Abschluss hat eine innere Funktion Zugriff auf Variablen in ihrer äußeren Funktion. Dies ist wahrscheinlich die einfachste einzeilige Erklärung, die Sie für Schließungen erhalten können.

Rakesh Pai
quelle
35
Das ist nur die halbe Erklärung. Bei Verschlüssen ist zu beachten, dass die alten Werte der äußeren Funktion für die innere Funktion weiterhin verfügbar sind, wenn nach dem Verlassen der äußeren Funktion immer noch auf die innere Funktion Bezug genommen wird.
Pcorcoran
22
Tatsächlich stehen der inneren Funktion nicht die alten Werte der äußeren Funktion zur Verfügung, sondern die alten Variablen , die möglicherweise neue Werte haben, wenn eine Funktion sie ändern könnte.
dlaliberte
86

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
Gerardo Lima
quelle
82

Du schläfst vorbei und lädst Dan ein. Sie fordern Dan auf, einen XBox-Controller mitzubringen.

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

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

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

Geben Sie hier die Bildbeschreibung ein

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

Der Prozess besteht aus zwei Schritten:

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

Geben Sie hier die Bildbeschreibung ein

Wenn eine Funktion erstellt wird, erhält sie eine versteckte Eigenschaft mit dem Namen [[Scope]], die auf die aktuelle LexicalEnvironment verweist.

Geben Sie hier die Bildbeschreibung ein

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-Kette bezeichnet werden kann.

Geben Sie hier die Bildbeschreibung ein

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

Verschlüsse

Eine verschachtelte Funktion kann nach Abschluss der äußeren Funktion weiter ausgeführt werden:

Geben Sie hier die Bildbeschreibung ein

Markieren von LexicalEnvironments:

Geben Sie hier die Bildbeschreibung ein

Wie wir sehen, this.say handelt es sich um eine Eigenschaft im Benutzerobjekt, die nach Abschluss des Benutzers weiterhin aktiv ist.

Und wenn Sie sich erinnern, wann this.sayes erstellt wird, erhält es (wie jede Funktion) eine interne Referenzthis.say.[[Scope]] beim Erstellen auf die aktuelle LexicalEnvironment. Die LexicalEnvironment der aktuellen Benutzerausführung bleibt also im Speicher. Alle Variablen des Benutzers sind auch seine Eigenschaften, daher werden sie auch sorgfältig aufbewahrt und nicht wie üblich verschmutzt.

Der springende Punkt ist sicherzustellen, dass die innere Funktion, wenn sie in Zukunft auf eine äußere Variable zugreifen möchte, dies tun kann.

Zusammenfassen:

  1. Die innere Funktion enthält einen Verweis auf die äußere LexicalEnvironment.
  2. Die innere Funktion kann jederzeit auf Variablen zugreifen, selbst 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 darauf verweist.

Dies wird als Schließung bezeichnet.

Arvand
quelle
78

JavaScript-Funktionen können auf Folgendes zugreifen:

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

Wenn eine Funktion auf ihre Umgebung zugreift, ist die Funktion ein Abschluss.

Beachten Sie, dass äußere Funktionen nicht erforderlich sind, obwohl sie Vorteile bieten, die ich hier nicht diskutiere. Durch den Zugriff auf Daten in seiner Umgebung hält ein Abschluss diese Daten am Leben. Im Unterfall der äußeren / inneren Funktionen kann eine äußere Funktion lokale Daten erstellen und schließlich beenden. Wenn jedoch eine oder mehrere innere Funktionen nach dem Verlassen 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 für die Stapelüberlauf-Abstimmungs- und Abstimmungsschaltflächen als Abschlüsse, voteUp_click und voteDown_click, implementiert sind, die Zugriff auf externe Variablen isVotedUp und isVotedDown haben, die global definiert sind. (Der Einfachheit halber beziehe ich mich auf die Frage-Abstimmungs-Schaltflächen von StackOverflow, nicht auf die Reihe der Antwort-Abstimmungs-Schaltflächen.)

Wenn der Benutzer auf die Schaltfläche VoteUp klickt, prüft die Funktion voteUp_click, ob isVotedDown == true ist, um zu bestimmen, ob eine Abstimmung nach oben oder nur nach unten abgebrochen werden soll. Die Funktion voteUp_click ist ein Abschluss, da sie auf ihre Umgebung zugreift.

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
}

Alle vier Funktionen sind Schließungen, da sie alle auf ihre Umgebung zugreifen.

John Pick
quelle
59

Als Vater eines 6-Jährigen, der derzeit kleine Kinder unterrichtet (und ein relativer Anfänger im Codieren ohne formale Ausbildung, sodass Korrekturen erforderlich sind), denke ich, dass die Lektion am besten durch praktisches Spielen erhalten bleibt. Wenn der 6-Jährige bereit ist zu verstehen, was eine Schließung ist, ist er alt genug, um es selbst zu versuchen. Ich würde vorschlagen, den Code in jsfiddle.net einzufügen, ein wenig zu erklären und sie in Ruhe zu lassen, um ein einzigartiges Lied zu erfinden. Der folgende erläuternde Text ist wahrscheinlich besser 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. Sie können es nicht berühren, riechen oder schmecken. Sie können es aufschreiben, sprechen und hören. Sie können es zum Erstellen verwenden mit einem Computer einen Geruch Geruch und Geschmack . Es kann von einem Computer unter Verwendung von Code nützlich gemacht werden.

CODE: Alle oben genannten Schriften 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 Interpreter 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, dass dies "jeder setzt sich" bedeutet - also tun Sie es alle. Die Klasse ist wie ein Computer und der russische Schüler ist der Dolmetscher. Für JavaScript wird der häufigste Interpreter als Browser bezeichnet.

BROWSER: Wenn Sie auf einem Computer, Tablet oder Telefon eine Verbindung zum Internet herstellen, um eine Website zu besuchen, verwenden Sie einen Browser. Beispiele, die Sie vielleicht 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 Fabrik. Es könnte eine kleine Fabrik mit nur einer Maschine sein. Oder es enthält viele andere kleine Fabriken mit jeweils vielen Maschinen, die unterschiedliche Aufgaben ausführen. In einer echten Kleiderfabrik könnten Unmengen von Stoffen und Fadenspulen hineingehen und T-Shirts und Jeans herauskommen. Unsere JavaScript-Fabrik verarbeitet nur Daten, kann weder nähen, bohren noch Metall schmelzen. In unserer JavaScript-Factory werden Daten eingegeben und Daten ausgegeben.

All diese Daten klingen ein bisschen 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. Du magst Hähnchenschenkel am liebsten, 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 zu treffen. Jede Zutat hat eine andere Garzeit und wir möchten, dass alles gleichzeitig vom Roboter heiß serviert wird. Wir müssen der Funktion die Daten darüber liefern, 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 Klammern. So was:

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

Beachten Sie dies /*...*/und //stoppen Sie das Lesen von Code durch den Browser.

NAME: Sie können eine Funktion so gut wie jedes gewünschte Wort aufrufen. Das Beispiel "cookMeal" ist typisch dafür, zwei Wörter zusammenzufügen und dem zweiten zu Beginn einen Großbuchstaben zu geben - dies ist jedoch nicht erforderlich. Es kann kein Leerzeichen enthalten und es kann keine eigene Zahl sein.

ELTERN: "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.

STREBEN: "Zahnspangen", die so aussehen, {}sind die getönten Scheiben unserer Fabrik. Von innerhalb der Fabrik kann man sehen, aber von außen kann man nicht sehen.

DAS LANGE CODE-BEISPIEL OBEN

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

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

Diese Funktion hat möglicherweise etwas mit dem Singen zu tun und benötigt möglicherweise Daten über eine Person. Es enthält Anweisungen, um etwas mit diesen Daten zu tun.

Nach der Funktion sing () befindet sich 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. Auf der Außenseite ist dieser Umschlag 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 Stück Schnur (als Schnur bezeichnet) miteinander verbunden sind und eine Phrase mit der Aufschrift "eine alte Dame" bilden. Unser Umschlag kann 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 geschrieben {}ist und Sie durch die getönten Fenster sehen können, wenn Sie sich innerhalb der geschweiften Klammern befinden, kann diese Variable von überall im Code aus gesehen werden. Wir nennen dies eine "globale Variable".

GLOBAL VARIABLE: Person ist eine globale Variable. Wenn Sie also ihren Wert von "einer alten Dame" in "einen jungen Mann" ändern , bleibt die Person ein junger Mann, bis Sie sich entscheiden, ihn erneut zu ändern, und dass jede andere Funktion in Der Code kann sehen, dass es ein junger Mann ist. Drücken Sie die F12Taste oder sehen Sie sich die Optionen an, um die Entwicklerkonsole eines Browsers zu öffnen, und geben Sie "person" ein, um zu sehen, was dieser Wert ist. Geben Sie ein person="a young man", um es zu ändern, und geben Sie dann erneut "Person" ein, um festzustellen, ob es sich geändert hat.

Danach haben wir die Leitung

sing(person);

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

"Komm sing , komm und hol Person !"

Wenn der Browser den JavaScript-Code geladen hat und diese Zeile erreicht hat, wird die Funktion gestartet. Ich setze die Zeile am Ende, um sicherzustellen, dass der Browser alle Informationen hat, die er zum Ausführen benötigt.

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

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

Die Verschlüsse wissen alle, wie die Variable firstPart der Funktion sing () lautet, da sie aus ihren getönten Fenstern heraussehen können.

Nach den Schließungen kommen die Linien

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

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

dankbar
quelle
56

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

Stellen Sie sich vor, Sie spielen mit Ihren kleinen Brüdern und Schwestern im ganzen Haus, bewegen sich mit Ihren Spielsachen und bringen einige davon in das Zimmer Ihres älteren Bruders. Nach einer Weile kehrte Ihr Bruder von der Schule zurück und ging in sein Zimmer. Er schloss sich darin ein, sodass Sie jetzt nicht mehr direkt auf Spielzeug zugreifen konnten, das dort zurückgelassen wurde. Aber du könntest an die Tür klopfen und deinen Bruder nach dem Spielzeug fragen. Dies nennt man Spielzeugs Schließung ; Dein Bruder hat es wieder gut gemacht, und er ist jetzt in äußerer Reichweite .

Vergleichen Sie mit einer Situation, in der eine Tür durch Zugluft verschlossen war und niemand drinnen war (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 jetzt gehen können ein anderes Spielzeug dort (neue Funktionsinstanz), aber nie die gleichen Spielzeuge bekommen, die in der ersten Rauminstanz übrig waren.

Für ein fortgeschrittenes Kind würde ich so etwas wie das Folgende sagen. Es ist nicht perfekt, aber man fühlt sich wie 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 Raum zurückgelassenen Spielzeuge weiterhin über den Bruder zugänglich, unabhängig davon, ob der Raum verschlossen ist. Hier ist ein Jsbin , mit dem man herumspielen kann.

dmi3y
quelle
49

Eine Antwort für einen Sechsjährigen (vorausgesetzt er weiß, was eine Funktion ist und was eine Variable ist und welche Daten es 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 nicht alle Variablen und Argumente, die in der Funktion verwendet wurden, die sie erstellt hat. Stattdessen wird diese übergeordnete Funktion "geschlossen". Mit anderen Worten, nichts kann hineinschauen und die verwendeten Variablen sehen, außer der zurückgegebenen Funktion. Diese neue Funktion hat eine besondere Fähigkeit, in die Funktion, die sie erstellt hat, zurückzublicken und die darin enthaltenen Daten anzuzeigen.

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 wirklich einfache Möglichkeit, dies zu erklären, 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.

dumm dumm
quelle
49

Eine Funktion in JavaScript ist nicht nur eine Referenz auf eine Reihe von Anweisungen (wie in der Sprache C), sondern enthält auch eine versteckte Datenstruktur, die aus Verweisen auf alle von ihr verwendeten nichtlokalen Variablen (erfasste Variablen) besteht. Solche zweiteiligen Funktionen werden Verschlüsse genannt. Jede Funktion in JavaScript kann als Abschluss betrachtet werden.

Verschlüsse sind Funktionen mit einem Zustand. Es ist "dies" in dem Sinne etwas ähnlich, dass "dies" auch den Status für eine Funktion bereitstellt, aber Funktion und "dies" separate Objekte sind ("dies" ist nur ein ausgefallener Parameter und die einzige Möglichkeit, ihn dauerhaft an a zu binden Funktion ist es, einen Verschluss zu erstellen). Während "dies" und die Funktion immer getrennt leben, kann eine Funktion nicht von ihrem Abschluss 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 als lokale Variablen einer Root-Funktion angenommen werden) und jede einzelne Ausführung einer Funktion neue Instanzen von erstellt Aus seinen lokalen Variablen folgt, dass jede Ausführung einer Funktion, die eine verschachtelte Funktion zurückgibt (oder auf andere Weise überträgt, z. B. als Rückruf registriert), einen neuen Abschluss erzeugt (mit einem eigenen potenziell eindeutigen Satz referenzierter nichtlokaler Variablen, die ihre Ausführung darstellen Kontext).

Es muss auch verstanden werden, dass lokale Variablen in JavaScript nicht auf dem Stapelrahmen, sondern auf dem Heap erstellt 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 weiterhin ungleich Null sein, wenn sie während der aktuellen Ausführung Teil eines Abschlusses wurden und weiterhin von ihren lexikalisch verschachtelten Funktionen referenziert werden (was nur möglich ist, wenn die Verweise auf Diese verschachtelten Funktionen wurden zurückgegeben oder auf andere Weise an 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();
srgstm
quelle
47

Vielleicht ein bisschen mehr als alles andere als der frühreifste Sechsjährige, aber ein paar Beispiele, die dazu beigetragen haben, dass das Konzept der Schließung in JavaScript für mich klickt.

Ein Abschluss ist eine Funktion, die Zugriff auf den Bereich einer anderen Funktion (ihre Variablen und Funktionen) hat. Der einfachste Weg, einen Abschluss zu erstellen, ist eine Funktion innerhalb einer Funktion. Der Grund dafür ist, dass eine Funktion in JavaScript immer Zugriff auf den Gültigkeitsbereich ihrer enthaltenen Funktion hat.

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

outerFunction();

ALERT: Affe

Im obigen Beispiel wird OuterFunction aufgerufen, was wiederum innerFunction aufruft. Beachten Sie, wie OuterVar für InnerFunction verfügbar ist. Dies wird durch die korrekte Warnung des Werts von OuterVar belegt.

Betrachten 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, wodurch einfach ein Verweis auf innerFunction zurückgegeben wird. Wenn referenceToInnerFunction aufgerufen wird, wird OuterVar zurückgegeben. Wie oben zeigt dies erneut, dass innerFunction Zugriff auf OuterVar hat, eine Variable von OuterFunction. Darüber hinaus ist es interessant festzustellen, dass dieser Zugriff auch nach Abschluss der Ausführung von OuterFunction erhalten bleibt.

Und hier wird es wirklich interessant. Wenn wir OuterFunction loswerden, sagen wir, setzen Sie es auf Null. Sie könnten 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());

ALERT: Affe ALERT: Affe

Aber wie ist das so? Wie kann referenceToInnerFunction den Wert von OuterVar noch kennen, nachdem OuterFunction auf Null gesetzt wurde?

Der Grund, warum referenceToInnerFunction weiterhin auf den Wert von OuterVar zugreifen kann, liegt darin, dass InnerFunction beim Erstellen des Abschlusses durch Platzieren von InnerFunction innerhalb von OuterFunction einen Verweis auf den Bereich von OuterFunction (seine Variablen und Funktionen) zu seiner Bereichskette hinzugefügt hat. Dies bedeutet, dass innerFunction einen Zeiger oder Verweis auf alle Variablen von OuterFunction hat, einschließlich OuterVar. Selbst wenn die Ausführung von OuterFunction abgeschlossen ist oder wenn sie gelöscht oder auf Null gesetzt wird, bleiben die Variablen in ihrem Gültigkeitsbereich wie OuterVar im Speicher, da die InnerFunction, auf die zurückgegeben wurde, noch nicht auf sie verweist referenceToInnerFunction. Um OuterVar und den Rest der Variablen von OuterFunction wirklich aus dem Speicher freizugeben, müssten Sie diesen hervorragenden Verweis auf sie entfernen.

///////////

Zwei weitere Dinge zu Schließungen zu beachten. Erstens hat der Abschluss immer Zugriff auf die letzten Werte seiner enthaltenen Funktion.

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

    innerFunction();
}

outerFunction();

Alarm: Gorilla

Zweitens behält ein Abschluss beim Erstellen einen Verweis auf alle Variablen und Funktionen seiner umschließenden Funktion bei. es kann nicht auswählen. Und so sollten Verschlüsse sparsam oder zumindest vorsichtig verwendet werden, da sie speicherintensiv sein können. Viele Variablen können lange nach Beendigung der Ausführung einer enthaltenen Funktion im Speicher gespeichert werden.

Michael Dziedzic
quelle
45

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

Und ja, ich würde es sogar einem 6-Jährigen empfehlen - wenn der 6-Jährige etwas über Schließungen lernt, ist es logisch, dass er bereit ist, die prägnante und einfache Erklärung im Artikel zu verstehen .

mjmoody383
quelle
Ich stimme zu: Die besagte Mozilla-Seite ist besonders einfach und prägnant. Erstaunlicherweise wurde Ihr Beitrag nicht so sehr geschätzt wie die anderen.
Brice Coustillas