537 Stimmen

Was ist eine "Schließung"?

Ich habe eine Frage zum Thema "Currying" gestellt, und es wurden Verschlüsse erwähnt. Was ist ein Abschluss? Wie hängt er mit Currying zusammen?

33 Stimmen

Was genau ist nun der Abschluss? Einige Antworten besagen, dass der Abschluss die Funktion ist. Andere sagen, es ist der Stapel. In einigen Antworten heißt es, es sei der "versteckte" Wert. Nach meinem Verständnis ist es die Funktion + eingeschlossene Variablen.

5 Stimmen

Erklärt, was ein Abschluss ist: stackoverflow.com/questions/4103750/

2 Stimmen

Sehen Sie sich auch an Was ist ein Abschluss? bei softwareengineering.stackexchange

940voto

superluminary Punkte 42702

Variabler Umfang

Wenn Sie eine lokale Variable deklarieren, hat diese Variable einen Geltungsbereich. Im Allgemeinen existieren lokale Variablen nur innerhalb des Blocks oder der Funktion, in der Sie sie deklarieren.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

Wenn ich versuche, auf eine lokale Variable zuzugreifen, suchen die meisten Sprachen danach im aktuellen Bereich und dann in den übergeordneten Bereichen, bis sie den Root-Bereich erreichen.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

Wenn ein Block oder eine Funktion beendet ist, werden seine lokalen Variablen nicht mehr benötigt und normalerweise aus dem Speicher gelöscht.

So erwarten wir normalerweise, dass die Dinge funktionieren.

Ein Abschluss ist ein dauerhafter lokaler Variablenbereich

Ein Closure ist ein beständiger Bereich, der lokale Variablen auch dann beibehält, wenn die Codeausführung diesen Block verlassen hat. Sprachen, die Closures unterstützen (z. B. JavaScript, Swift und Ruby), ermöglichen es Ihnen, einen Verweis auf einen Bereich (einschließlich seiner übergeordneten Bereiche) beizubehalten, auch nachdem der Block, in dem diese Variablen deklariert wurden, die Ausführung beendet hat, sofern Sie irgendwo einen Verweis auf diesen Block oder diese Funktion aufbewahren.

Das Scope-Objekt und alle seine lokalen Variablen sind an die Funktion gebunden und bleiben so lange bestehen, wie die Funktion bestehen bleibt.

Dies ermöglicht die Übertragbarkeit von Funktionen. Wir können davon ausgehen, dass alle Variablen, die bei der ersten Definition der Funktion im Anwendungsbereich waren, auch dann noch im Anwendungsbereich sind, wenn wir die Funktion später aufrufen, selbst wenn wir die Funktion in einem völlig anderen Kontext aufrufen.

Zum Beispiel

Hier ist ein einfaches Beispiel in JavaScript, das die Sache verdeutlicht:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Hier habe ich eine Funktion innerhalb einer Funktion definiert. Die innere Funktion hat Zugriff auf alle lokalen Variablen der äußeren Funktion, einschließlich a . Die Variable a ist für die innere Funktion gültig.

Normalerweise werden beim Beenden einer Funktion alle ihre lokalen Variablen gelöscht. Wenn wir jedoch die innere Funktion zurückgeben und sie einer Variablen zuweisen fnc so dass sie auch nach outer verlassen hat, alle Variablen, die in den Anwendungsbereich fielen, als inner definiert wurde, bestehen auch . Die Variable a wurde geschlossen über - es ist innerhalb einer Schließung.

Beachten Sie, dass die Variable a ist völlig privat für fnc . Dies ist eine Möglichkeit, private Variablen in einer funktionalen Programmiersprache wie JavaScript zu erstellen.

Wie Sie sich vielleicht denken können, rufe ich, wenn ich fnc() druckt es den Wert von a die "1" ist.

In einer Sprache ohne Abschluss ist die Variable a in den Müll geworfen worden wäre, wenn die Funktion outer verlassen. Der Aufruf von fnc hätte einen Fehler ausgelöst, weil a nicht mehr existiert.

In JavaScript wird die Variable a bleibt bestehen, da der Variablenbereich bei der ersten Deklaration der Funktion erstellt wird und so lange bestehen bleibt, wie die Funktion existiert.

a gehört in den Anwendungsbereich von outer . Der Umfang der inner hat einen übergeordneten Zeiger auf den Bereich von outer . fnc ist eine Variable, die auf inner . a so lange bestehen bleibt, wie fnc bleibt bestehen. a innerhalb des Verschlusses liegt.

Weitere Lektüre (beobachten)

Ich habe eine YouTube-Video diesen Code mit einigen praktischen Anwendungsbeispielen zu betrachten.

2 Stimmen

Könnte ich ein Beispiel dafür haben, wie dies in einer Bibliothek wie JQuery funktioniert, wie im vorletzten Absatz angegeben? Ich habe das nicht ganz verstanden.

8 Stimmen

Hallo Jubbat, ja, öffnen Sie jquery.js und schauen Sie sich die erste Zeile an. Du wirst sehen, dass eine Funktion geöffnet ist. Springen Sie nun zum Ende und Sie sehen window.jQuery = window.$ = jQuery. Dann wird die Funktion geschlossen und selbst ausgeführt. Sie haben nun Zugriff auf die Funktion $, die wiederum Zugriff auf die anderen in der Closure definierten Funktionen hat. Ist damit Ihre Frage beantwortet?

0 Stimmen

@superluminary Danke für die tolle Erklärung. Aber ich habe eine Frage in Bezug auf Javas Unterstützung für Closures. Ich habe zwar gelesen, dass Java keine Closures unterstützt, aber nach deiner Erklärung scheint es doch so zu sein. Nehmen wir an, Sie haben eine öffentliche Methode, die eine Instanz einer anonymen Klasse erzeugen kann, die Implementierung der anonymen Klasse kann die lokalen Variablen der Funktion verwenden. Bedeutet dies, dass Java Abschlüsse unterstützt?

130voto

Kyle Cronin Punkte 74993

Ich werde ein Beispiel geben (in JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...

Diese Funktion, makeCounter, gibt eine Funktion zurück, die wir x genannt haben, und die bei jedem Aufruf um eins hochzählt. Da wir x keine Parameter übergeben, muss sie sich die Anzahl irgendwie merken. Er weiß, wo er zu finden ist, und zwar aufgrund des so genannten lexikalischen Scopings - er muss an der Stelle suchen, an der er definiert ist, um den Wert zu finden. Dieser "versteckte" Wert wird als Closure bezeichnet.

Hier ist noch einmal mein Beispiel für das Curry:

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

var add3 = add(3);

add3(4); returns 7

Sie sehen, dass beim Aufruf von add mit dem Parameter a (der 3 ist) dieser Wert in der Schließung der zurückgegebenen Funktion enthalten ist, die wir als add3 definieren. Wenn wir add3 aufrufen, weiß es also, wo es den Wert a finden muss, um die Addition durchzuführen.

4 Stimmen

IDK, welche Sprache (wahrscheinlich F#) Sie in der obigen Sprache verwendet haben. Könnten Sie bitte obiges Beispiel in Pseudocode geben? Ich habe schwer Zeit, dies zu verstehen.

1 Stimmen

4 Stimmen

@KyleCronin Tolles Beispiel, danke. F: Ist es korrekter zu sagen "der versteckte Wert wird als Abschluss bezeichnet", oder ist "die Funktion, die den Wert versteckt, ist der Abschluss"? Oder "der Prozess des Versteckens des Wertes ist die Schließung"? Vielen Dank!

104voto

SasQ Punkte 12961

Erstens, im Gegensatz zu dem, was die meisten Leute hier sagen, Verschluss ist pas eine Funktion ! Was also es es?
Es ist ein einstellen. von Symbolen, die im "umgebenden Kontext" einer Funktion definiert sind (bekannt als ihr Umwelt ), die ihn zu einem GESCHLOSSENEN Ausdruck machen (d.h. ein Ausdruck, in dem jedes Symbol definiert ist und einen Wert hat, so dass er ausgewertet werden kann).

Zum Beispiel, wenn Sie eine JavaScript-Funktion haben:

function closed(x) {
  return x + 3;
}

Es ist ein geschlossener Ausdruck weil alle darin vorkommenden Symbole darin definiert sind (ihre Bedeutung ist klar), so dass man sie auswerten kann. Mit anderen Worten, sie ist Eigenständig .

Aber wenn Sie eine Funktion wie diese haben:

function open(x) {
  return x*y + 3;
}

es ist ein offener Ausdruck weil sie Symbole enthält, die darin nicht definiert sind. Nämlich, y . Wenn wir uns diese Funktion ansehen, können wir nicht sagen, was y ist und was er bedeutet, kennen wir nicht, so dass wir diesen Ausdruck nicht auswerten können. D.h. wir können diese Funktion erst aufrufen, wenn wir wissen, was y darin bedeuten soll. Diese y wird als freie Variable .

Diese y bittet um eine Definition, aber diese Definition ist nicht Teil der Funktion - sie wird an anderer Stelle definiert, in ihrem "umgebenden Kontext" (auch bekannt als die Umwelt ). Zumindest hoffen wir das :P

Sie könnte zum Beispiel global definiert werden:

var y = 7;

function open(x) {
  return x*y + 3;
}

Oder sie könnte in einer Funktion definiert werden, die sie umschließt:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

Der Teil der Umgebung, der den freien Variablen in einem Ausdruck ihre Bedeutung gibt, ist die Verschluss . Sie wird so genannt, weil sie ein öffnen Ausdruck in eine geschlossen ein, indem sie diese fehlenden Definitionen für alle ihre freie Variablen damit wir sie bewerten können.

Im obigen Beispiel ist die innere Funktion (der wir keinen Namen gegeben haben, weil wir sie nicht brauchen) eine offener Ausdruck weil die Variable y Darin ist kostenlos - ihre Definition liegt außerhalb der Funktion, in der Funktion, die sie umhüllt. Die Umwelt für diese anonyme Funktion ist die Menge der Variablen:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Nun, die Verschluss ist der Teil dieser Umwelt, der schließt die innere Funktion durch Angabe der Definitionen für alle ihre freie Variablen . In unserem Fall war die einzige freie Variable in der inneren Funktion y so dass der Abschluss dieser Funktion diese Teilmenge ihrer Umgebung ist:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Die beiden anderen in der Umgebung definierten Symbole sind pas Teil des Verschluss dieser Funktion, da sie für die Ausführung nicht erforderlich sind. Sie werden nicht benötigt, um schließen es.

Mehr über die Theorie dahinter erfahren Sie hier: https://stackoverflow.com/a/36878651/434562

Es ist erwähnenswert, dass im obigen Beispiel die Wrapper-Funktion ihre innere Funktion als Wert zurückgibt. Der Zeitpunkt, zu dem wir diese Funktion aufrufen, kann zeitlich weit von dem Zeitpunkt entfernt sein, zu dem die Funktion definiert (oder erstellt) wurde. Insbesondere läuft die Wrapper-Funktion nicht mehr, und ihre Parameter, die sich auf dem Aufrufstapel befanden, sind nicht mehr da :P Das ist ein Problem, denn die innere Funktion benötigt y da zu sein, wenn sie aufgerufen wird! Mit anderen Worten, die Variablen aus der Schließung müssen irgendwie überleben die Wrapper-Funktion und ist da, wenn sie gebraucht wird. Daher muss die innere Funktion eine Schnappschuss dieser Variablen, die den Abschluss bilden, und speichert sie an einem sicheren Ort zur späteren Verwendung. (Irgendwo außerhalb des Aufrufstapels.)

Aus diesem Grund wird der Begriff oft verwechselt Verschluss zu dieser speziellen Art von Funktion gehören, die solche Schnappschüsse der externen Variablen, die sie verwenden, oder der Datenstruktur, die zur Speicherung dieser Variablen für spätere Zwecke verwendet wird, erstellen kann. Aber ich hoffe, Sie verstehen jetzt, dass es sich um pas die Schließung selbst - sie sind nur Mittel, um implementieren Abschlüsse in einer Programmiersprache oder Sprachmechanismen, die es ermöglichen, dass die Variablen des Funktionsabschlusses bei Bedarf zur Verfügung stehen. Es gibt eine Menge Missverständnisse rund um Closures, die dieses Thema (unnötigerweise) viel verwirrender und komplizierter machen, als es eigentlich ist.

7 Stimmen

Eine Analogie, die Anfängern helfen könnte, ist ein Verschluss schließt alle losen Enden zusammen was eine Person tut, wenn sie Schließung anstreben (oder es löst auf. alle notwendigen Referenzen, oder ...). Nun, es hat mir geholfen, es so zu sehen :o)

5 Stimmen

Ich habe im Laufe der Jahre viele Definitionen des Begriffs "Abschluss" gelesen, aber ich glaube, diese ist bisher meine Lieblingsdefinition. Ich schätze, wir alle haben unsere eigene Art, Konzepte wie dieses geistig abzubilden, und diese hier stimmt sehr gut mit meiner überein.

3 Stimmen

Ich habe eine ganze Reihe von Erklärungen auf Google, Youtube, in Büchern, Blogs usw. gesehen, und sie sind alle sinnvoll und gut, aber ich denke, dies ist die logischste und klarste Erklärung.

65voto

Ben Childs Punkte 4230

Kyles Antwort ist ziemlich gut. Ich denke, die einzige zusätzliche Klarstellung ist, dass die Schließung im Grunde ein Schnappschuss des Stapels zu dem Zeitpunkt ist, an dem die Lambda-Funktion erstellt wird. Wenn die Funktion dann erneut ausgeführt wird, wird der Stack wieder in den Zustand vor der Ausführung der Funktion versetzt. Wie Kyle erwähnt, ist der versteckte Wert ( count ) verfügbar ist, wenn die Lambda-Funktion ausgeführt wird.

16 Stimmen

Es ist nicht nur der Stack, sondern auch die umschließenden lexikalischen Bereiche, die erhalten bleiben, unabhängig davon, ob sie auf dem Stack oder dem Heap (oder beiden) gespeichert sind.

33voto

John Millikin Punkte 190278

Ein Abschluss ist eine Funktion, die auf den Zustand einer anderen Funktion verweisen kann. In Python wird zum Beispiel die Schließung "inner" verwendet:

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X