it-swarm-eu.dev

Was ist eine Schließung?

Hin und wieder sehe ich, dass "Verschlüsse" erwähnt werden, und ich habe versucht, sie nachzuschlagen, aber das Wiki gibt keine Erklärung, die ich verstehe. Könnte mir hier jemand helfen?

157
gablin

(Haftungsausschluss: Dies ist eine grundlegende Erklärung. Was die Definition betrifft, vereinfache ich ein wenig.)

Die einfachste Art, sich einen Abschluss vorzustellen, ist eine Funktion, die als Variable gespeichert werden kann (als "erstklassige Funktion" bezeichnet), die über eine besondere Fähigkeit verfügt, auf andere lokale Variablen zuzugreifen den Bereich, in dem es erstellt wurde.

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 Funktionen1 Zugewiesen an document.onclick und displayValOfBlack sind Verschlüsse. Sie können sehen, dass beide auf die boolesche Variable black verweisen, diese Variable jedoch außerhalb der Funktion zugewiesen ist. Da black lokal für den Bereich ist, in dem die Funktion definiert wurde , bleibt der Zeiger auf diese Variable erhalten.

Wenn Sie dies in eine HTML-Seite einfügen:

  1. Klicken Sie, um zu Schwarz zu wechseln
  2. Drücken Sie [Enter], um "true" zu sehen.
  3. Klicken Sie erneut und wechselt wieder zu Weiß
  4. Drücken Sie die [Eingabetaste], um "false" anzuzeigen.

Dies zeigt, dass beide Zugriff auf dasselbe black haben und zum Speichern des Status verwendet werden können. ohne Wrapper-Objekt.

Der Aufruf von setKeyPress soll zeigen, wie eine Funktion wie jede Variable übergeben werden kann. Das im Abschluss erhaltene scope ist immer noch dasjenige, in dem die Funktion definiert wurde.

Abschlüsse werden häufig als Ereignishandler verwendet, insbesondere in JavaScript und ActionScript. Durch die gute Verwendung von Closures können Sie Variablen implizit an Ereignishandler binden, ohne einen Objekt-Wrapper erstellen zu müssen. Eine unachtsame Verwendung führt jedoch zu Speicherverlusten (z. B. wenn nur ein nicht verwendeter, aber erhaltener Ereignishandler große Objekte im Speicher, insbesondere DOM-Objekte, festhält und die Speicherbereinigung verhindert).


1: Tatsächlich sind alle Funktionen in JavaScript Abschlüsse.

141
Nicole

Ein Verschluss ist im Grunde nur eine andere Sichtweise auf ein Objekt. Ein Objekt sind Daten, an die eine oder mehrere Funktionen gebunden sind. Ein Abschluss ist eine Funktion, an die eine oder mehrere Variablen gebunden sind. Die beiden sind grundsätzlich identisch, zumindest auf Implementierungsebene. Der wirkliche Unterschied besteht darin, woher sie kommen.

Bei der objektorientierten Programmierung deklarieren Sie eine Objektklasse, indem Sie ihre Elementvariablen und ihre Methoden (Elementfunktionen) im Voraus definieren, und erstellen dann Instanzen dieser Klasse. Jede Instanz wird mit einer vom Konstruktor initialisierten Kopie der Mitgliedsdaten geliefert. Sie haben dann eine Variable eines Objekttyps und geben sie als Datenelement weiter, da der Fokus auf ihrer Natur als Daten liegt.

Bei einem Abschluss hingegen wird das Objekt 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 sich auf eine der lokalen Variablen der äußeren Funktion beziehen, und der Compiler erkennt dies und verschiebt diese Variablen aus dem Stapelbereich der äußeren Funktion in die versteckte Objektdeklaration des Abschlusses. Sie haben dann eine Variable eines Verschlusstyps, und obwohl es sich im Grunde genommen um ein Objekt unter der Haube handelt, geben Sie sie als Funktionsreferenz weiter, da der Fokus auf ihrer Natur als Funktion liegt.

69
Mason Wheeler

Der Begriff Closure kommt von der Tatsache, dass ein Code (Block, Funktion) freie Variablen haben kann, die sind. geschlossen (dh an einen Wert gebunden) durch die Umgebung, in der der Codeblock definiert ist.

Nehmen Sie zum Beispiel die Funktionsdefinition Scala:

def addConstant(v: Int): Int = v + k

Im Funktionskörper gibt es zwei Namen (Variablen) v und k, die zwei ganzzahlige Werte angeben. Der Name v ist gebunden, weil er als Argument der Funktion addConstant deklariert ist (wenn wir uns die Funktionsdeklaration ansehen, wissen wir, dass v bei der Funktion ein Wert zugewiesen wird wird aufgerufen). Der Name k ist für die Funktion addConstant frei, da die Funktion keinen Hinweis darauf enthält, an welchen Wert k gebunden ist (und wie).

Um einen Anruf wie folgt auszuwerten:

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 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 addConstant in einem Kontext definiert haben, in dem k definiert ist, ist addConstant zu einem Abschluss geworden weil alle freien Variablen jetzt geschlossen sind (an einen Wert gebunden): addConstant kann aufgerufen und weitergegeben werden, als ob es waren eine Funktion. Beachten Sie, dass die freie Variable k an einen Wert gebunden ist, wenn der Abschluss definiert ist , während die Argumentvariable v wird gebunden, wenn der Abschluss aufgerufen wird .

Ein Abschluss ist also im Grunde eine Funktion oder ein Codeblock, der über seine freien Variablen auf nicht lokale Werte zugreifen kann, nachdem diese durch den Kontext gebunden wurden.

Wenn Sie in vielen Sprachen einen Verschluss nur einmal verwenden, können Sie ihn 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.

29
Giorgio

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 Namespace der zurückgegebenen Funktion alertValue wird mit dem Namespace verbunden, in dem sich die Variable closure befindet. Wenn Sie die gesamte Funktion löschen, wird der Wert der Variablen closure gelöscht. Bis dahin kann die Funktion alertValue jedoch immer den Wert der Variablen closure.

Wenn Sie diesen Code ausführen, weist die erste Iteration der Variablen closure den Wert 0 zu und schreibt die Funktion neu in:

var closure_example = function(){
    alertValue : function(){
        closure++;
        alert(closure);
    }       
}

Und da alertValue die lokale Variable closure benötigt, um die Funktion auszuführen, bindet sie sich an den Wert der zuvor zugewiesenen lokalen Variablen closure.

Und jetzt wird jedes Mal, wenn Sie die Funktion closure_example Aufrufen, der inkrementierte Wert der Variablen closure ausgeschrieben, da alert(closure) gebunden ist.

closure_example.alertValue()//alerts value 1 
closure_example.alertValue()//alerts value 2 
closure_example.alertValue()//alerts value 3
//etc. 
9
Muha

Ein "Abschluss" ist im Wesentlichen ein lokaler Zustand und ein Code, die zu einem Paket zusammengefasst sind. Normalerweise stammt der lokale Status 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, das leider etwas schwer zu erklären ist, weil es unbekannt ist.

Eine Analogie, die ich in der Vergangenheit erfolgreich verwendet habe, war: "Stellen Sie sich vor, wir haben etwas, das wir" das Buch "nennen, in der Raumschließung." Das Buch "ist die Kopie dort, in der Ecke, von TAOCP, aber auf der Tischschließung Es ist diese Kopie eines Dresdner Aktenbuchs. Je nachdem, in welchem ​​Abschluss Sie sich befinden, führt der Code "Gib mir das Buch" dazu, dass verschiedene Dinge passieren. "

5
Vatine

Es ist schwer zu definieren, was Schließung ist, ohne den Begriff „Staat“ zu definieren.

Grundsätzlich passiert in einer Sprache mit vollständigem lexikalischem Umfang, die Funktionen als erstklassige Werte behandelt, etwas Besonderes. Wenn ich etwas tun würde wie:

function foo(x)
return x
end

x = foo

Die Variable x verweist nicht nur auf function foo(), sondern auch auf den Status foo, der bei der letzten Rückgabe belassen wurde. Die wahre Magie entsteht, wenn foo andere Funktionen hat, die in seinem Bereich weiter definiert sind; Es ist wie eine eigene Mini-Umgebung (genau wie "normal" definieren wir Funktionen in einer globalen Umgebung).

Funktionell kann es viele der gleichen Probleme lösen wie das Schlüsselwort 'static' von C++ (C?), Das den Status einer lokalen Variablen während mehrerer Funktionsaufrufe beibehält. Es ist jedoch eher so, als würde man dasselbe Prinzip (statische Variable) auf eine Funktion anwenden, da Funktionen erstklassige Werte sind. Das Schließen unterstützt den zu speichernden Status der gesamten Funktion (nichts mit den statischen Funktionen von C++ zu tun).

Wenn Sie Funktionen als erstklassige Werte behandeln und die Unterstützung für Schließungen hinzufügen, können Sie auch mehr als eine Instanz derselben Funktion im Speicher haben (ähnlich wie bei Klassen). Dies bedeutet, dass Sie denselben Code wiederverwenden können, ohne den Funktionsstatus zurücksetzen zu müssen, wie dies beim Umgang mit statischen C++ - Variablen innerhalb einer Funktion erforderlich ist (kann dies falsch sein?).

Hier sind einige Tests der Schließunterstützung 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 variiert wahrscheinlich von Sprache zu Sprache, aber es scheint in Lua, dass bei jeder Ausführung einer Funktion ihr Status zurückgesetzt wird. Ich sage dies, weil die Ergebnisse des obigen Codes anders wären, wenn wir direkt auf die Funktion/den Zustand myclosure zugreifen würden (anstatt über die zurückgegebene anonyme Funktion), da pvalue zurückgesetzt würde bis 10; Wenn wir jedoch über x (die anonyme Funktion) auf den Status von myclosure zugreifen, können Sie sehen, dass pvalue lebt 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 C++ 11-Leck (außer dem, was in früheren Versionen enthalten ist). Beachten Sie also, dass dies kein Vergleich zwischen Schließungen in C++ 11 und Lua ist. Außerdem sind alle von Lua nach C++ gezogenen Linien Ähnlichkeiten, da statische Variablen und Verschlü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.

5
Trae Barlow

Ein Abschluss ist eine Funktion, der der Status zugeordnet ist:

In Perl erstellen Sie Verschlü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 neuen Funktionen 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");
}
4
Martin York

Betrachten wir eine einfache Funktion:

function f1(x) {
    // ... something
}

Diese Funktion wird als Funktion der obersten Ebene bezeichnet, da sie in keiner anderen Funktion verschachtelt ist. Jede JavaScript-Funktion ordnet sich eine Liste von Objekten zu, die als "Scope Chain" bezeichnet werden. Diese Bereichskette ist eine geordnete Liste von Objekten. Jedes dieser Objekte definiert einige Variablen.

In Funktionen der obersten Ebene besteht die Bereichskette aus einem einzelnen Objekt, dem globalen Objekt. Die obige Funktion f1 Enthält beispielsweise eine Bereichskette mit einem einzelnen Objekt, das alle globalen Variablen definiert. (Beachten Sie, dass der Begriff "Objekt" hier kein JavaScript-Objekt bedeutet, sondern lediglich ein implementierungsdefiniertes Objekt, das als Variablencontainer fungiert, in dem JavaScript Variablen "nachschlagen" kann.)

Wenn diese Funktion aufgerufen wird, erstellt JavaScript ein sogenanntes "Aktivierungsobjekt" und setzt es an die Spitze der Bereichskette. Dieses Objekt enthält alle lokalen Variablen (zum Beispiel x hier). Daher haben wir jetzt zwei Objekte in der Bereichskette: Das erste ist das Aktivierungsobjekt und darunter das globale Objekt.

Beachten Sie sehr sorgfältig, dass die beiden Objekte zu VERSCHIEDENEN Zeiten in die Bereichskette eingefügt werden. Das globale Objekt wird gesetzt, wenn die Funktion definiert ist (d. H. Wenn JavaScript die Funktion analysiert und das Funktionsobjekt erstellt hat), und das Aktivierungsobjekt wird eingegeben, wenn die Funktion aufgerufen wird.

Das wissen wir jetzt:

  • Jeder Funktion ist eine Bereichskette zugeordnet
  • Wenn die Funktion definiert ist (wenn das Funktionsobjekt erstellt wird), speichert JavaScript eine Bereichskette mit dieser Funktion
  • Bei Funktionen der obersten Ebene enthält die Bereichskette zum Zeitpunkt der Funktionsdefinition nur das globale Objekt und fügt zum Zeitpunkt des Aufrufs ein zusätzliches Aktivierungsobjekt hinzu

Die Situation wird interessant, wenn wir uns mit verschachtelten Funktionen befassen. Erstellen wir also eine:

function f1(x) {

    function f2(y) {
        // ... something
    }

}

Wenn f1 Definiert wird, erhalten wir eine Bereichskette dafür, die nur das globale Objekt enthält.

Wenn nun 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. Beachten Sie auch, dass f2 Definiert wird. Daher speichert JavaScript zu diesem Zeitpunkt auch eine neue Bereichskette für f2. Die für diese innere Funktion gespeicherte Bereichskette ist die aktuell gültige Bereichskette. Die aktuell gültige Bereichskette ist die von f1 's. Daher ist die Bereichskette von f2 Die Bereichskette von f1 current - die das Aktivierungsobjekt von f1 Und enthält das globale Objekt.

Wenn f2 Aufgerufen wird, erhält es ein eigenes Aktivierungsobjekt, das y enthält und zu seiner Bereichskette hinzugefügt wird, die bereits das Aktivierungsobjekt von f1 Und das globale Objekt enthält.

Wenn in f2 Eine andere verschachtelte Funktion definiert wäre, würde ihre Gültigkeitsbereichskette zur Definitionszeit drei Objekte enthalten (2 Aktivierungsobjekte von zwei äußeren Funktionen und das globale Objekt) und zum Aufrufzeitpunkt 4.

Jetzt verstehen wir also, wie die Scope-Kette funktioniert, haben aber noch nicht über Schließungen gesprochen.

Die Kombination eines Funktionsobjekts und eines Bereichs (einer Reihe von Variablenbindungen), in dem die Variablen der Funktion aufgelöst werden, wird in der Informatikliteratur als Abschluss bezeichnet - JavaScript, der endgültige Leitfaden von David Flanagan

Die meisten Funktionen werden mit derselben Bereichskette aufgerufen, die zum Zeitpunkt der Definition der Funktion gültig war, und es spielt keine Rolle, ob es sich um einen Abschluss handelt. Abschlüsse werden interessant, wenn sie unter einer anderen Gültigkeitsbereichskette aufgerufen werden als die, die zum Zeitpunkt ihrer Definition gültig war. Dies geschieht am häufigsten, wenn ein verschachteltes Funktionsobjekt zurückgegeben von der Funktion ist, in der es definiert wurde.

Wenn die Funktion zurückkehrt, wird dieses Aktivierungsobjekt aus der Bereichskette entfernt. Wenn keine verschachtelten Funktionen vorhanden waren, gibt es keine Verweise 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 innerhalb ihrer äußeren Funktion geblieben sind, werden sie selbst zusammen mit dem Aktivierungsobjekt, auf das sie sich beziehen, als Müll gesammelt. Wenn die Funktion jedoch eine verschachtelte Funktion definiert und sie zurückgibt oder irgendwo 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 sich bezieht, wird auch kein Müll gesammelt.

In unserem obigen Beispiel geben wir f2 Nicht von f1 Zurück. Wenn also ein Aufruf von f1 Zurückkehrt, wird das Aktivierungsobjekt aus der Gültigkeitsbereichskette und dem Müll entfernt gesammelt. Aber wenn wir so etwas hätten:

function f1(x) {

    function f2(y) {
        // ... something
    }

    return f2;
}

Hier hat das zurückgegebene f2 Eine Bereichskette, die das Aktivierungsobjekt von f1 Enthält, und daher wird kein Müll gesammelt. Wenn wir zu diesem Zeitpunkt f2 Aufrufen, kann es auf die Variable x von f1 Zugreifen, obwohl wir nicht mehr in f1 Sind.

Daher können wir sehen, dass eine Funktion ihre Bereichskette mit sich behält und mit der Bereichskette alle Aktivierungsobjekte der äußeren Funktionen kommen. Dies ist die Essenz 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, im Gegensatz zu dem Bereich, der aktiv war, als sie aufgerufen wurden.

Es gibt eine Reihe leistungsfähiger Programmiertechniken, die Abschlüsse wie die Approximation privater Variablen, ereignisgesteuerte Programmierung, Teilanwendung usw. umfassen.

Beachten Sie auch, dass all dies für alle Sprachen gilt, die Schließungen unterstützen. Zum Beispiel PHP (5.3+), Python, Ruby usw.

2
treecoder