Hin und wieder sehe ich, dass "Abschlüsse" erwähnt werden, und ich habe versucht, nachzuschlagen, aber Wiki gibt keine Erklärung, die ich verstehe. Könnte mir hier jemand helfen?
language-features
closures
gablin
quelle
quelle
Antworten:
(Haftungsausschluss: Dies ist eine grundlegende Erklärung. Was die Definition angeht, vereinfache ich ein wenig.)
Die einfachste Art, sich einen Abschluss vorzustellen, ist eine Funktion, die als Variable gespeichert werden kann (als "erstklassige Funktion" bezeichnet) und die über die besondere Fähigkeit verfügt, auf andere Variablen zuzugreifen, die lokal zu dem Bereich gehören, in dem sie erstellt wurden.
Beispiel (JavaScript):
Die Funktionen 1 zugewiesen
document.onclick
unddisplayValOfBlack
sind Verschlüsse. Sie können sehen, dass beide auf die boolesche Variable verweisenblack
, diese Variable jedoch außerhalb der Funktion zugewiesen wird. Dablack
ist auf den Umfang lokale wobei die Funktion definiert wurde , wird der Zeiger auf diese Variable erhalten.Wenn Sie dies in eine HTML-Seite einfügen:
Dies zeigt, dass beide Zugriff auf dasselbe Objekt haben
black
und zum Speichern des Status ohne Wrapper-Objekt verwendet werden können.Der Aufruf von
setKeyPress
soll demonstrieren, wie eine Funktion genau wie eine Variable übergeben werden kann. Der Gültigkeitsbereich , der im Abschluß erhalten bleibt, ist immer noch derjenige, in dem die Funktion definiert wurde.Closures werden häufig als Ereignishandler verwendet, insbesondere in JavaScript und ActionScript. Durch die ordnungsgemäße Verwendung von Closures können Sie Variablen implizit an Ereignishandler binden, ohne einen Objektwrapper erstellen zu müssen. Eine unachtsame Verwendung führt jedoch zu Speicherverlusten (z. B. wenn ein nicht verwendeter, aber erhaltener Ereignishandler das einzige ist, an dem große Objekte im Speicher, insbesondere DOM-Objekte, festgehalten werden, wodurch die Garbage Collection verhindert wird).
1: Eigentlich sind alle Funktionen in JavaScript Abschlüsse.
quelle
black
in einer Funktion deklariert ist, würde das nicht zerstört werden, wenn sich der Stapel abwickelt ...?black
wird in einer Funktion deklariert, würde das nicht zerstört werden." Denken Sie auch daran, dass das Objekt erhalten bleibt, wenn Sie ein Objekt in einer Funktion deklarieren und es dann einer Variablen zuweisen, die sich an einer anderen Stelle befindet, da es andere Verweise darauf gibt.Ein Verschluss ist im Grunde nur eine andere Sichtweise auf ein Objekt. Ein Objekt besteht aus Daten, an die eine oder mehrere Funktionen gebunden sind. Ein Abschluß ist eine Funktion, an die eine oder mehrere Variablen gebunden sind. Die beiden sind im Grunde genommen identisch, zumindest auf Implementierungsebene. Der wirkliche Unterschied liegt darin, woher sie kommen.
Bei der objektorientierten Programmierung deklarieren Sie eine Objektklasse, indem Sie ihre Elementvariablen und ihre Methoden (Elementfunktionen) im Voraus definieren und dann Instanzen dieser Klasse erstellen. Jede Instanz wird mit einer Kopie der Mitgliedsdaten geliefert, die vom Konstruktor initialisiert wurden. Sie haben dann eine Variable eines Objekttyps und übergeben sie als Datenelement, da der Fokus auf ihrer Art als Daten liegt.
In einem Closure wird das Objekt hingegen nicht wie eine Objektklasse im Voraus definiert oder durch einen Konstruktoraufruf in Ihrem Code instanziiert. Stattdessen schreiben Sie den Abschluss als Funktion in eine andere Funktion. Der Abschluss kann auf jede lokale Variable der äußeren Funktion verweisen, und der Compiler erkennt dies und verschiebt diese Variablen aus dem Stapelbereich der äußeren Funktion in die Wimmelbilddeklaration des Abschlusses. Sie haben dann eine Variable eines Verschlusstyps, und obwohl es sich im Grunde um ein Objekt unter der Haube handelt, geben Sie es als Funktionsreferenz weiter, da der Fokus auf seiner Art als Funktion liegt.
quelle
Der Begriff Closure ergibt sich aus der Tatsache, dass ein Codeteil (Block, Funktion) freie Variablen haben kann, die durch die Umgebung, in der der Codeblock definiert ist , geschlossen (dh an einen Wert gebunden) werden.
Nehmen Sie zum Beispiel die Scala-Funktionsdefinition:
In der Funktionskörper sind zwei Namen (Variablen)
v
undk
angibt zwei ganzzahlige Werte. Der Namev
ist gebunden, weil er als Argument der Funktion deklariert istaddConstant
(wenn wir uns die Funktionsdeklaration ansehen, wissen wir, dassv
dieser beim Aufruf der Funktion ein Wert zugewiesen wird). Der Namek
ist für die Funktion frei,addConstant
da die Funktion keinen Hinweis darauf enthält, an welchen Wertk
(und wie) dieser Wert gebunden ist.Um einen Anruf wie folgt zu bewerten:
wir müssen
k
einen Wert zuweisen , was nur passieren kann, wenn der Namek
in dem Kontext definiert ist, in dem eraddConstant
definiert ist. Zum Beispiel:Nachdem wir nun
addConstant
in einem Kontextk
definiert haben, in dem dies definiert ist,addConstant
wurde es zu einem Abschluss, da alle seine freien Variablen jetzt geschlossen sind (an einen Wert gebunden sind): SieaddConstant
können aufgerufen und herumgereicht werden, als ob es eine Funktion wäre. Beachten Sie, dass die freie Variablek
beim Definieren des Abschlusses an einen Wert gebunden ist , während die Argumentvariablev
beim Aufrufen des Abschlusses gebunden ist .Ein Closure ist also im Grunde genommen ein Funktions- oder Codeblock, der über seine freien Variablen auf nicht lokale Werte zugreifen kann, nachdem diese durch den Kontext gebunden wurden.
Wenn Sie einen Verschluss nur einmal verwenden, können Sie ihn in vielen Sprachen anonymisieren , z
Beachten Sie, dass eine Funktion ohne freie Variablen ein Sonderfall eines Abschlusses ist (mit einem leeren Satz freier Variablen). Analog ist eine anonyme Funktion ein Sonderfall eines anonymen Abschlusses , dh eine anonyme Funktion ist ein anonymer Abschluss ohne freie Variablen.
quelle
Eine einfache Erklärung in JavaScript:
alert(closure)
verwendet den zuvor erstellten Wert vonclosure
. DeralertValue
Namespace der zurückgegebenen Funktion wird mit dem Namespace verbunden, in dem sich dieclosure
Variable befindet. Wenn Sie die gesamte Funktion löschen, wird der Wert derclosure
Variablen gelöscht, aber bis dahin kann diealertValue
Funktion den Wert der Variablen immer lesen / schreibenclosure
.Wenn Sie diesen Code ausführen, weist die erste Iteration der
closure
Variablen den Wert 0 zu und schreibt die Funktion folgendermaßen um:Und da
alertValue
die lokale Variableclosure
zum Ausführen der Funktion benötigt wird, bindet sie sich an den Wert der zuvor zugewiesenen lokalen Variablenclosure
.Und jetzt wird jedes Mal, wenn Sie die
closure_example
Funktion aufrufen , der inkrementierte Wert derclosure
Variablen ausgegeben, da diesealert(closure)
gebunden ist.quelle
Ein "Abschluß" ist im wesentlichen ein lokaler Zustand und ein Code, die zu einem Paket zusammengefaßt sind. Typischerweise stammt der lokale Zustand aus einem umgebenden (lexikalischen) Bereich, und der Code ist (im Wesentlichen) eine innere Funktion, die dann nach außen zurückgegeben wird. Der Abschluss ist dann eine Kombination der erfassten Variablen, die die innere Funktion sieht, und des Codes der inneren Funktion.
Es ist eines dieser Dinge, die leider ein bisschen schwer zu erklären sind, weil sie unbekannt sind.
Eine Analogie, die ich in der Vergangenheit erfolgreich verwendet habe, war: "Stellen Sie sich vor, wir haben etwas, das wir" das Buch "nennen, im Raumabschluss," das Buch "ist das Exemplar dort, in der Ecke, von TAOCP, aber auf dem Tischabschluss , es ist die Kopie eines Dresdner Aktenbuchs. Je nachdem, in welchem Abschluss Sie sich befinden, führt der Code "Gib mir das Buch" zu unterschiedlichen Ereignissen. "
quelle
static
lokalen Variablen als Abschluß betrachtet werden? Beziehen Schließungen in Haskell den Staat mit ein?static
lokalen Variablen haben Sie genau eine).Es ist schwer zu definieren, was ein Abschluss ist, ohne den Begriff „Staat“ zu definieren.
Grundsätzlich passiert in einer Sprache mit vollständigem lexikalischem Geltungsbereich, in der Funktionen als erstklassige Werte behandelt werden, etwas Besonderes. Wenn ich etwas machen würde wie:
Die Variable
x
verweist nicht nur auffunction foo()
den Status, sondern auch auf den Status,foo
den sie beim letzten Zurückgeben belassen hat. Die wahre Magie entsteht, wennfoo
andere Funktionen in ihrem Geltungsbereich näher definiert sind. Es ist wie eine eigene Mini-Umgebung (genau wie "normal" definieren wir Funktionen in einer globalen Umgebung).Funktional kann es viele der gleichen Probleme lösen wie das Schlüsselwort 'static' in C ++ (C?), Das den Status einer lokalen Variablen bei mehreren Funktionsaufrufen beibehält. Es ist jedoch eher so, als würde man dasselbe Prinzip (statische Variable) auf eine Funktion anwenden, da Funktionen erstklassige Werte sind. Closure fügt Unterstützung für das Speichern des gesamten Funktionszustands hinzu (hat nichts mit den statischen Funktionen von C ++ zu tun).
Das Behandeln von Funktionen als erstklassige Werte und das Hinzufügen von Unterstützung für Abschlüsse bedeutet auch, dass Sie mehr als eine Instanz derselben Funktion im Speicher haben können (ähnlich wie bei Klassen). Dies bedeutet, dass Sie denselben Code wiederverwenden können, ohne den Funktionsstatus zurücksetzen zu müssen, wie dies erforderlich ist, wenn Sie mit statischen C ++ - Variablen in einer Funktion arbeiten (kann dies falsch sein?).
Hier finden Sie einige Tests zur Unterstützung der Schließung von Lua.
Ergebnisse:
Es kann schwierig werden, und es variiert wahrscheinlich von Sprache zu Sprache, aber in Lua scheint es, dass der Status jedes Mal zurückgesetzt wird, wenn eine Funktion ausgeführt wird. Ich sage das, weil die Ergebnisse aus dem obigen Code anders wären, wenn wir
myclosure
direkt auf die Funktion / den Zustand zugreifen würden (anstatt über die anonyme Funktion, die sie zurückgibt), dapvalue
sie auf 10 zurückgesetzt würden. aber wenn wir über x (die anonyme Funktion) auf den Status von myclosure zugreifen, können Sie sehen, dasspvalue
das lebendig ist und sich irgendwo im Speicher befindet. Ich vermute, es steckt ein bisschen mehr dahinter, vielleicht kann jemand die Art der Implementierung besser erklären.PS: Ich kenne kein bisschen C ++ 11 (anders als in früheren Versionen). Beachten Sie also, dass dies kein Vergleich zwischen Closures in C ++ 11 und Lua ist. Alle Linien, die von Lua nach C ++ gezogen werden, sind Ähnlichkeiten, da statische Variablen und Abschlüsse nicht zu 100% gleich sind. auch wenn sie manchmal verwendet werden, um ähnliche Probleme zu lösen.
Ich bin mir nicht sicher, ob im obigen Codebeispiel die anonyme Funktion oder die Funktion höherer Ordnung als Abschluss betrachtet wird.
quelle
Ein Abschluss ist eine Funktion mit folgendem Status:
In Perl erstellen Sie Abschlüsse wie folgt:
Wenn wir uns die neue Funktionalität von C ++ ansehen.
Außerdem können Sie den aktuellen Status an das Objekt binden:
quelle
Betrachten wir eine einfache Funktion:
Diese Funktion wird als Top-Level-Funktion bezeichnet, da sie in keiner anderen Funktion verschachtelt ist. Jede JavaScript-Funktion verknüpft sich mit einer Liste von Objekten, die als "Scope Chain" bezeichnet werden . Diese Scope-Kette ist eine geordnete Liste von Objekten. Jedes dieser Objekte definiert einige Variablen.
In Funktionen der obersten Ebene besteht die Scope-Kette aus einem einzelnen Objekt, dem globalen Objekt. Die
f1
obige Funktion verfügt beispielsweise über eine Bereichskette, in der sich ein einzelnes Objekt befindet, das alle globalen Variablen definiert. (Beachten Sie, dass der Begriff "Objekt" hier kein JavaScript-Objekt bedeutet. Es handelt sich lediglich um ein implementierungsdefiniertes Objekt, das als Variablencontainer fungiert und in dem JavaScript Variablen "nachschlagen" kann.)Wenn diese Funktion aufgerufen wird, erstellt JavaScript ein sogenanntes "Aktivierungsobjekt" und setzt es an die Spitze der Scope-Kette. Dieses Objekt enthält alle lokalen Variablen (zum Beispiel
x
hier). Daher haben wir jetzt zwei Objekte in der Scope-Kette: Das erste ist das Aktivierungsobjekt und darunter das globale Objekt.Beachten Sie sehr sorgfältig, dass die beiden Objekte zu unterschiedlichen Zeiten in die Scope-Kette aufgenommen werden. Das globale Objekt wird abgelegt, wenn die Funktion definiert ist (dh wenn JavaScript die Funktion analysiert und das Funktionsobjekt erstellt hat), und das Aktivierungsobjekt wird beim Aufrufen der Funktion eingegeben.
Nun wissen wir also:
Interessant wird die Situation, wenn es sich um verschachtelte Funktionen handelt. Also, lasst uns einen erstellen:
Wenn
f1
definiert wird, erhalten wir eine Scope-Kette, die nur das globale Objekt enthält.Jetzt, wenn es
f1
aufgerufen wird, erhält die Scope-Kette vonf1
das Aktivierungsobjekt. Dieses Aktivierungsobjekt enthält die Variablex
und die Variable,f2
die eine Funktion ist. Und beachten Sie,f2
dass definiert wird. Daher speichert JavaScript zu diesem Zeitpunkt auch eine neue Bereichskette fürf2
. Die für diese innere Funktion gespeicherte Scope-Kette ist die aktuell gültige Scope-Kette. Die derzeitige Gültigkeitskette ist die vonf1
. Daher istf2
die Scope Chainf1
die aktuelle Scope Chain, die das Aktivierungsobjekt vonf1
und das globale Objekt enthält.Wenn
f2
aufgerufen wird, erhält es ein eigenes Aktivierungsobjekty
, das der Gültigkeitsbereichskette hinzugefügt wurde, die bereits das Aktivierungsobjekt vonf1
und das globale Objekt enthält.Wenn eine andere verschachtelte Funktion definiert wäre
f2
, würde ihre Gültigkeitsbereichskette zum Definitionszeitpunkt drei Objekte (2 Aktivierungsobjekte von zwei äußeren Funktionen und das globale Objekt) und 4 zum Aufrufzeitpunkt enthalten.Jetzt verstehen wir, wie die Scope-Kette funktioniert, haben aber noch nicht über Schließungen gesprochen.
Die meisten Funktionen werden mit derselben Bereichskette aufgerufen, die bei der Definition der Funktion wirksam war, und es spielt keine Rolle, ob es sich um einen Abschluss handelt. Abschlüsse werden interessant, wenn sie in einer anderen Gültigkeitsbereichskette aufgerufen werden als die, die zum Zeitpunkt ihrer Definition gültig war. Dies geschieht am häufigsten , wenn ein verschachtelten Funktion Objekt wird zurückgegeben von der Funktion , in dem sie festgelegt wurde.
Wenn die Funktion zurückgegeben wird, wird dieses Aktivierungsobjekt aus der Bereichskette entfernt. Wenn es keine verschachtelten Funktionen gibt, gibt es keine Referenzen mehr auf das Aktivierungsobjekt und es wird Müll gesammelt. Wenn verschachtelte Funktionen definiert wurden, hat jede dieser Funktionen einen Verweis auf die Bereichskette, und diese Bereichskette bezieht sich auf das Aktivierungsobjekt.
Wenn diese verschachtelten Funktionsobjekte jedoch in ihrer äußeren Funktion verbleiben, werden sie selbst zusammen mit dem Aktivierungsobjekt, auf das sie sich bezogen haben, mit Müll gesammelt. Wenn die Funktion jedoch eine verschachtelte Funktion definiert und diese zurückgibt oder in einer Eigenschaft speichert, gibt es einen externen Verweis auf die verschachtelte Funktion. Es wird kein Müll gesammelt und das Aktivierungsobjekt, auf das es verweist, wird auch kein Müll gesammelt.
In unserem obigen Beispiel kehren wir nicht
f2
von zurückf1
. Wenn also ein Aufruf von " return" erfolgtf1
, wird sein Aktivierungsobjekt aus seiner Gültigkeitsbereichskette entfernt und Garbage gesammelt. Aber wenn wir so etwas hätten:Hier hat die Rückgabe
f2
eine Bereichskette, die das Aktivierungsobjekt von enthältf1
, und daher wird kein Müll gesammelt. Wenn wir an dieser Stelle anrufenf2
, kann es auf dief1
Variable zugreifen ,x
auch wenn wir nicht da sindf1
.Daher können wir sehen, dass eine Funktion ihre Gültigkeitskette bei sich behält, und mit der Gültigkeitskette kommen alle Aktivierungsobjekte der äußeren Funktionen. Dies ist das Wesen der Schließung. Wir sagen, dass Funktionen in JavaScript "lexikalisch" sind , was bedeutet, dass sie den Bereich speichern, der aktiv war, als sie definiert wurden, und nicht den Bereich, der aktiv war, als sie aufgerufen wurden.
Es gibt eine Reihe leistungsfähiger Programmiertechniken, die das Annähern von privaten Variablen, ereignisgesteuerte Programmierung, Teilanwendung usw. umfassen.
Beachten Sie auch, dass all dies für alle Sprachen gilt, die Closures unterstützen. Zum Beispiel PHP (5.3+), Python, Ruby usw.
quelle
Ein Closure ist eine Compiler-Optimierung (alias syntaktischer Zucker?). Einige Leute haben dies auch als das Objekt des Armen bezeichnet .
Siehe die Antwort von Eric Lippert : (Auszug unten)
Der Compiler generiert Code wie folgt:
Sinn ergeben?
Sie haben auch nach Vergleichen gefragt. Sowohl VB als auch JScript erstellen Abschlüsse auf die gleiche Art und Weise.
quelle