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?
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?
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):
var setKeyPress = function(callback) {
document.onkeypress = callback;
};
var initialize = function() {
var black = false;
document.onclick = function() {
black = !black;
document.body.style.backgroundColor = black ? "#000000" : "transparent";
}
var displayValOfBlack = function() {
alert(black);
}
setKeyPress(displayValOfBlack);
};
initialize();
Die Funktionen 1 zugewiesen document.onclick
und displayValOfBlack
sind Verschlüsse. Sie können sehen, dass beide auf die boolesche Variable verweisen black
, diese Variable jedoch außerhalb der Funktion zugewiesen wird. Da black
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.
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.
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:
def addConstant(v: Int): Int = v + k
In der Funktionskörper sind zwei Namen (Variablen) v
und k
angibt zwei ganzzahlige Werte. Der Name v
ist gebunden, weil er als Argument der Funktion deklariert ist addConstant
(wenn wir uns die Funktionsdeklaration ansehen, wissen wir, dass v
dieser beim Aufruf der Funktion ein Wert zugewiesen wird). Der Name k
ist für die Funktion frei, addConstant
da die Funktion keinen Hinweis darauf enthält, an welchen Wert k
(und wie) dieser Wert gebunden ist.
Um einen Anruf wie folgt zu bewerten:
val n = addConstant(10)
wir müssen k
einen Wert zuweisen , was nur passieren kann, wenn der Name k
in dem Kontext definiert ist, in dem er addConstant
definiert ist. Zum Beispiel:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
def addConstant(v: Int): Int = v + k
values.map(addConstant)
}
Nachdem wir nun addConstant
in einem Kontext k
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): Sie addConstant
können aufgerufen und herumgereicht werden, als ob es eine Funktion wäre. Beachten Sie, dass die freie Variable k
beim Definieren des Abschlusses an einen Wert gebunden ist , während die Argumentvariable v
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
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
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.
Eine einfache Erklärung in JavaScript:
var closure_example = function() {
var closure = 0;
// after first iteration the value will not be erased from the memory
// because it is bound with the returned alertValue function.
return {
alertValue : function() {
closure++;
alert(closure);
}
};
};
closure_example();
alert(closure)
verwendet den zuvor erstellten Wert von closure
. Der alertValue
Namespace der zurückgegebenen Funktion wird mit dem Namespace verbunden, in dem sich die closure
Variable befindet. Wenn Sie die gesamte Funktion löschen, wird der Wert der closure
Variablen gelöscht, aber bis dahin kann die alertValue
Funktion den Wert der Variablen immer lesen / schreiben closure
.
Wenn Sie diesen Code ausführen, weist die erste Iteration der closure
Variablen den Wert 0 zu und schreibt die Funktion folgendermaßen um:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
Und da alertValue
die lokale Variable closure
zum Ausführen der Funktion benötigt wird, bindet sie sich an den Wert der zuvor zugewiesenen lokalen Variablen closure
.
Und jetzt wird jedes Mal, wenn Sie die closure_example
Funktion aufrufen , der inkrementierte Wert der closure
Variablen ausgegeben, da diese alert(closure)
gebunden ist.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
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. "
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:
function foo(x)
return x
end
x = foo
Die Variable x
verweist nicht nur auf function foo()
den Status, sondern auch auf den Status, foo
den sie beim letzten Zurückgeben belassen hat. Die wahre Magie entsteht, wenn foo
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.
--Closure testing
--By Trae Barlow
--
function myclosure()
print(pvalue)--nil
local pvalue = pvalue or 10
return function()
pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
print(pvalue)
pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
return pvalue
end
end
x = myclosure() --x now references anonymous function inside myclosure()
x()--nil, 20
x() --21, 31
x() --32, 42
--43, 53 -- if we iterated x() again
Ergebnisse:
nil
20
31
42
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), da pvalue
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, dass pvalue
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.
Ein Abschluss ist eine Funktion mit folgendem Status:
In Perl erstellen Sie Abschlüsse wie folgt:
#!/usr/bin/perl
# This function creates a closure.
sub getHelloPrint
{
# Bind state for the function we are returning.
my ($first) = @_;a
# The function returned will have access to the variable $first
return sub { my ($second) = @_; print "$first $second\n"; };
}
my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");
&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World
Wenn wir uns die neue Funktionalität von C ++ ansehen.
Außerdem können Sie den aktuellen Status an das Objekt binden:
#include <string>
#include <iostream>
#include <functional>
std::function<void(std::string const&)> getLambda(std::string const& first)
{
// Here we bind `first` to the function
// The second parameter will be passed when we call the function
return [first](std::string const& second) -> void
{ std::cout << first << " " << second << "\n";
};
}
int main(int argc, char* argv[])
{
auto hw = getLambda("Hello");
auto gw = getLambda("GoodBye");
hw("World");
gw("World");
}
Betrachten wir eine einfache Funktion:
function f1(x) {
// ... something
}
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:
function f1(x) {
function f2(y) {
// ... something
}
}
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 von f1
das Aktivierungsobjekt. Dieses Aktivierungsobjekt enthält die Variable x
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ür f2
. Die für diese innere Funktion gespeicherte Scope-Kette ist die aktuell gültige Scope-Kette. Die derzeitige Gültigkeitskette ist die von f1
. Daher ist f2
die Scope Chain f1
die aktuelle Scope Chain, die das Aktivierungsobjekt von f1
und das globale Objekt enthält.
Wenn f2
aufgerufen wird, erhält es ein eigenes Aktivierungsobjekt y
, das der Gültigkeitsbereichskette hinzugefügt wurde, die bereits das Aktivierungsobjekt von f1
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 Kombination eines Funktionsobjekts und eines Gültigkeitsbereichs (eine Reihe von Variablenbindungen), in denen die Variablen der Funktion aufgelöst werden, wird in der Informatikliteratur als Closure bezeichnet - JavaScript, das definitive Handbuch von David Flanagan
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ück f1
. Wenn also ein Aufruf von " return" erfolgt f1
, wird sein Aktivierungsobjekt aus seiner Gültigkeitsbereichskette entfernt und Garbage gesammelt. Aber wenn wir so etwas hätten:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Hier hat die Rückgabe f2
eine Bereichskette, die das Aktivierungsobjekt von enthält f1
, und daher wird kein Müll gesammelt. Wenn wir an dieser Stelle anrufen f2
, kann es auf die f1
Variable zugreifen , x
auch wenn wir nicht da sind f1
.
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.
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:
private class Locals
{
public int count;
public void Anonymous()
{
this.count++;
}
}
public Action Counter()
{
Locals locals = new Locals();
locals.count = 0;
Action counter = new Action(locals.Anonymous);
return counter;
}
Sinn ergeben?
Sie haben auch nach Vergleichen gefragt. Sowohl VB als auch JScript erstellen Abschlüsse auf die gleiche Art und Weise.