Was ist eine Schließung?


432

Ich stellte eine Frage zu Currying und Verschlüsse wurden erwähnt. Was ist eine Schließung? In welcher Beziehung steht es zum Curry?


22
Was genau ist nun die Schließung ??? Einige Antworten sagen, dass der Verschluss die Funktion ist. Einige sagen, es ist der Stapel. Einige Antworten sagen, es ist der "versteckte" Wert. Nach meinem Verständnis ist es die Funktion + eingeschlossene Variablen.
Roland

3
Erklärt, was eine Schließung ist: stackoverflow.com/questions/4103750/…
dietbuddha

Schauen Sie sich auch Was ist ein Verschluss? bei softwareengineering.stackexchange
B12Toaster

Erklärt, was ein Verschluss ist und welcher Anwendungsfall häufig vorkommt: trungk18.com/experience/javascript-closure
Sasuke91 vor

Antworten:


742

Variabler Umfang

Wenn Sie eine lokale Variable deklarieren, hat diese Variable einen Gültigkeitsbereich. 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 im aktuellen Bereich danach und dann durch die übergeordneten Bereiche, bis sie den Stammbereich erreichen.

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

Wenn ein Block oder eine Funktion erledigt ist, werden seine lokalen Variablen nicht mehr benötigt und sind normalerweise nicht mehr genügend Speicher.

So erwarten wir normalerweise, dass die Dinge funktionieren.

Ein Abschluss ist ein persistenter lokaler Variablenbereich

Ein Abschluss ist ein dauerhafter Bereich, der lokale Variablen auch dann beibehält, wenn die Codeausführung diesen Block verlassen hat. In Sprachen, die das Schließen unterstützen (wie JavaScript, Swift und Ruby), können Sie einen Verweis auf einen Bereich (einschließlich der übergeordneten Bereiche) beibehalten, auch nachdem der Block, in dem diese Variablen deklariert wurden, die Ausführung abgeschlossen hat, sofern Sie einen Verweis behalten zu diesem Block oder Funktion irgendwo.

Das Bereichsobjekt und alle seine lokalen Variablen sind an die Funktion gebunden und bleiben bestehen, solange diese Funktion bestehen bleibt.

Dies gibt uns Funktionsportabilität. Wir können davon ausgehen, dass alle Variablen, die zum Zeitpunkt der ersten Definition der Funktion im Gültigkeitsbereich waren, beim späteren Aufruf der Funktion noch im Gültigkeitsbereich sind, auch wenn wir die Funktion in einem völlig anderen Kontext aufrufen.

Zum Beispiel

Hier ist ein wirklich einfaches Beispiel in JavaScript, das den Punkt veranschaulicht:

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 erhält Zugriff auf alle lokalen Variablen der äußeren Funktion, einschließlich a. Die Variable aist im Bereich für die innere Funktion.

Normalerweise werden beim Beenden einer Funktion alle lokalen Variablen weggeblasen. Wenn wir jedoch die innere Funktion zurückgeben und sie einer Variablen zuweisen, fncsodass sie nach dem outerBeenden bestehen bleibt , bleiben auch alle Variablen erhalten, die zum innerZeitpunkt der Definition im Gültigkeitsbereich waren . Die Variable awurde geschlossen - sie befindet sich innerhalb eines Abschlusses.

Beachten Sie, dass die Variable afür völlig privat ist fnc. Auf diese Weise können private Variablen in einer funktionalen Programmiersprache wie JavaScript erstellt werden.

Wie Sie vielleicht erraten können, gibt es beim Aufrufen fnc()den Wert von a"1" aus.

In einer Sprache ohne Abschluss awäre die Variable beim outerBeenden der Funktion durch Müll gesammelt und weggeworfen worden . Das Aufrufen von fnc hätte einen Fehler ausgelöst, da dieser anicht mehr vorhanden ist.

In JavaScript ableibt die Variable bestehen, da der Variablenbereich beim ersten Deklarieren der Funktion erstellt wird und so lange bestehen bleibt, wie die Funktion weiterhin besteht.

agehört zum Geltungsbereich von outer. Der Bereich von innerhat einen übergeordneten Zeiger auf den Bereich von outer. fncist eine Variable, die auf zeigt inner. ableibt so lange fncbestehen. aist innerhalb der Schließung.


116
Ich fand das ein ziemlich gutes und leicht verständliches Beispiel.
user12345613

16
Vielen Dank für die tolle Erklärung, ich habe viele gesehen, aber dies ist die Zeit, in der ich es wirklich verstanden habe.
Dimitar Dimitrov

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

6
Hallo Jubbat, ja, öffne jquery.js und schau dir die erste Zeile an. Sie werden sehen, dass eine Funktion geöffnet ist. Wenn Sie jetzt zum Ende springen, sehen Sie window.jQuery = window. $ = JQuery. Dann wird die Funktion geschlossen und selbst ausgeführt. Sie haben jetzt Zugriff auf die $ -Funktion, die wiederum Zugriff auf die anderen im Abschluss definierten Funktionen hat. Beantwortet das deine Frage?
Superleuchte

4
Beste Erklärung im Web. Viel einfacher als ich dachte
Mantis

95

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 zur Verfügung stellen, muss es sich irgendwie an die Anzahl erinnern. Es weiß, wo es zu finden ist, basierend auf dem sogenannten lexikalischen Scoping - es muss auf die Stelle schauen, an der es definiert ist, um den Wert zu finden. Dieser "versteckte" Wert wird als Abschluss bezeichnet.

Hier ist noch einmal mein Curry-Beispiel:

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

var add3 = add(3);

add3(4); returns 7

Wenn Sie add mit dem Parameter a (3) aufrufen, ist dieser Wert im Abschluss der zurückgegebenen Funktion enthalten, die wir als add3 definieren. Auf diese Weise weiß es, wenn wir add3 aufrufen, wo sich der a-Wert befindet, um die Addition durchzuführen.


4
IDK, welche Sprache (wahrscheinlich F #) Sie in der obigen Sprache verwendet haben. Könnten Sie bitte das obige Beispiel im Pseudocode angeben? Es fällt mir schwer, das zu verstehen.
Benutzer


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

2
@ RobertHume Gute Frage. Semantisch ist der Begriff "Schließung" etwas mehrdeutig. Meine persönliche Definition ist, dass die Kombination sowohl des verborgenen Wertes als auch der Verwendung durch die einschließende Funktion den Abschluss darstellt.
Kyle Cronin

1
@KyleCronin Danke - Ich habe mittelfristig am Montag ein Programm. :) Wollte das "Verschluss" -Konzept fest in meinem Kopf haben. Vielen Dank, dass Sie diese großartige Antwort auf die Frage von OP veröffentlicht haben!

58

Kyles Antwort ist ziemlich gut. Ich denke, die einzige zusätzliche Klarstellung ist, dass der Abschluss im Grunde eine Momentaufnahme des Stapels an dem Punkt ist, an dem die Lambda-Funktion erstellt wird. Wenn die Funktion erneut ausgeführt wird, wird der Stapel in diesem Zustand wiederhergestellt, bevor die Funktion ausgeführt wird. Wie Kyle erwähnt, ist dieser versteckte Wert ( count) verfügbar, wenn die Lambda-Funktion ausgeführt wird.


14
Es ist nicht nur der Stapel - es sind die einschließenden lexikalischen Bereiche, die erhalten bleiben, unabhängig davon, ob sie auf dem Stapel oder auf dem Heap (oder beiden) gespeichert sind.
Matt Fenwick

38

Erstens ist die Schließung im Gegensatz zu dem, was die meisten Leute hier sagen, keine Funktion ! Also was ist das?
Es handelt sich um eine Reihe von Symbolen, die im "umgebenden Kontext" einer Funktion (bekannt als ihre Umgebung ) definiert sind und sie zu einem GESCHLOSSENEN Ausdruck machen (dh zu einem Ausdruck, in dem jedes Symbol definiert ist und einen Wert hat, damit es ausgewertet werden kann).

Zum Beispiel, wenn Sie eine JavaScript-Funktion haben:

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

Es ist ein geschlossener Ausdruck, da alle darin vorkommenden Symbole darin definiert sind (ihre Bedeutung ist klar), sodass Sie ihn auswerten können. Mit anderen Worten, es ist in sich geschlossen .

Aber wenn Sie eine Funktion wie diese haben:

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

Es ist ein offener Ausdruck, weil es Symbole enthält, die darin nicht definiert wurden. Nämlich , y. Wenn wir uns diese Funktion ansehen, können wir nicht sagen, was sie yist und was sie bedeutet. Wir kennen ihren Wert nicht und können diesen Ausdruck daher nicht bewerten. Das heißt, wir können diese Funktion erst aufrufen, wenn wir sagen, was ydarin bedeuten soll. Dies ywird als freie Variable bezeichnet .

Dies yerfordert eine Definition, aber diese Definition ist nicht Teil der Funktion - sie wird an einer anderen Stelle in ihrem "umgebenden Kontext" (auch als Umgebung bezeichnet ) definiert. Zumindest hoffen wir darauf: P.

Zum Beispiel könnte es global definiert werden:

var y = 7;

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

Oder es könnte in einer Funktion definiert werden, die es 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 der Abschluss . Es wird so genannt, weil es einen offenen Ausdruck in einen geschlossenen Ausdruck verwandelt , indem diese fehlenden Definitionen für alle seine freien Variablen angegeben werden , damit wir ihn bewerten können.

In dem obigen Beispiel ist die innere Funktion (die wir keinen Namen gegeben haben , weil wir es nicht brauchten) ist ein offener Ausdruck , da die Variable ydarin ist frei - seine Definition außerhalb der Funktion, in der Funktion , die es hüllt . Die Umgebung 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`]
}

Der Abschluss ist nun der Teil dieser Umgebung, der die innere Funktion schließt, indem er die Definitionen für alle seine freien Variablen bereitstellt . In unserem Fall war die einzige freie Variable in der inneren Funktion y, daher ist der Abschluss dieser Funktion diese Teilmenge ihrer Umgebung:

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

Die beiden anderen in der Umgebung definierten Symbole sind nicht Teil des Abschlusses dieser Funktion, da sie nicht ausgeführt werden müssen. Sie werden nicht benötigt, um es zu schließen .

Mehr zur Theorie dahinter hier: https://stackoverflow.com/a/36878651/434562

Es ist zu beachten, dass im obigen Beispiel die Wrapper-Funktion ihre innere Funktion als Wert zurückgibt. Der Moment, in dem wir diese Funktion aufrufen, kann zeitlich von dem Moment entfernt sein, an dem die Funktion definiert (oder erstellt) wurde. Insbesondere wird seine Wrapping-Funktion nicht mehr ausgeführt und seine Parameter, die sich auf dem Aufrufstapel befanden, sind nicht mehr vorhanden: P Dies stellt ein Problem dar, da die innere Funktion beim Aufrufen vorhanden sein muss y! Mit anderen Worten, es erfordert, dass die Variablen von ihrem Abschluss die Wrapper-Funktion irgendwie überleben und bei Bedarf da sind. Daher muss die innere Funktion eine Momentaufnahme dieser Variablen erstellen, die geschlossen werden, und sie für die spätere Verwendung an einem sicheren Ort speichern. (Irgendwo außerhalb des Aufrufstapels.)

Aus diesem Grund wird der Begriff " Schließen" häufig als eine spezielle Art von Funktion verwechselt, mit der solche Schnappschüsse der von ihnen verwendeten externen Variablen oder der Datenstruktur, in der diese Variablen für später gespeichert werden, erstellt werden können. Aber ich hoffe, Sie verstehen jetzt, dass sie nicht der Abschluss selbst sind - sie sind nur Möglichkeiten, Abschlüsse in einer Programmiersprache oder Sprachmechanismen zu implementieren , die es ermöglichen, dass die Variablen aus dem Abschluss der Funktion bei Bedarf vorhanden sind. Es gibt viele Missverständnisse in Bezug auf Schließungen, die dieses Thema (unnötigerweise) viel verwirrender und komplizierter machen, als es tatsächlich ist.


1
Eine Analogie, die Anfängern dabei helfen könnte, ist ein Verschluss , der alle losen Enden zusammenhält , was eine Person tut, wenn sie einen Verschluss anstrebt (oder alle notwendigen Referenzen auflöst oder ...). Nun, es hat mir geholfen, es so zu sehen: o)
Will Crawford

Ich habe im Laufe der Jahre viele Definitionen von Schließung gelesen, aber ich denke, diese ist meine bisherige Lieblingsdefinition. Ich denke, wir haben alle unsere eigene Art, Konzepte wie dieses und dieses mental abzubilden.
Jason S.

29

Ein Abschluss ist eine Funktion, die auf den Status einer anderen Funktion verweisen kann. In Python wird beispielsweise der Verschluss "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

23

Um das Verständnis von Schließungen zu erleichtern, kann es hilfreich sein, zu untersuchen, wie sie in einer Verfahrenssprache implementiert werden können. Diese Erklärung folgt einer vereinfachten Implementierung von Schließungen in Schema.

Zunächst muss ich das Konzept eines Namespace einführen. Wenn Sie einen Befehl in einen Scheme-Interpreter eingeben, muss dieser die verschiedenen Symbole im Ausdruck auswerten und ihren Wert ermitteln. Beispiel:

(define x 3)

(define y 4)

(+ x y) returns 7

Die Definitionsausdrücke speichern den Wert 3 an der Stelle für x und den Wert 4 an der Stelle für y. Wenn wir dann (+ xy) aufrufen, sucht der Interpreter nach den Werten im Namespace und kann die Operation ausführen und 7 zurückgeben.

In Schema gibt es jedoch Ausdrücke, mit denen Sie den Wert eines Symbols vorübergehend überschreiben können. Hier ist ein Beispiel:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

Mit dem Schlüsselwort let wird ein neuer Namespace mit x als Wert 5 eingeführt. Sie werden feststellen, dass y immer noch 4 ist, sodass die zurückgegebene Summe 9 beträgt. Sie können dies auch sehen, wenn der Ausdruck x beendet hat ist wieder 3. In diesem Sinne wurde x vorübergehend durch den lokalen Wert maskiert.

Prozedurale und objektorientierte Sprachen haben ein ähnliches Konzept. Immer wenn Sie eine Variable in einer Funktion deklarieren, die denselben Namen wie eine globale Variable hat, erhalten Sie denselben Effekt.

Wie würden wir das umsetzen? Ein einfacher Weg ist mit einer verknüpften Liste - der Kopf enthält den neuen Wert und der Schwanz enthält den alten Namespace. Wenn Sie ein Symbol nachschlagen müssen, beginnen Sie am Kopf und arbeiten sich den Schwanz hinunter.

Kommen wir nun zur Implementierung erstklassiger Funktionen. Eine Funktion ist mehr oder weniger eine Reihe von Anweisungen, die ausgeführt werden müssen, wenn die Funktion aufgerufen wird und im Rückgabewert gipfelt. Wenn wir eine Funktion einlesen, können wir diese Anweisungen hinter den Kulissen speichern und beim Aufruf der Funktion ausführen.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

Wir definieren x als 3 und plus-x als seinen Parameter y plus den Wert von x. Schließlich rufen wir plus-x in einer Umgebung auf, in der x durch ein neues x maskiert wurde, dieses mit dem Wert 5. Wenn wir lediglich die Operation (+ xy) für die Funktion plus-x speichern, da wir uns im Kontext befinden Wenn x 5 ist, wäre das zurückgegebene Ergebnis 9. Dies wird als dynamisches Scoping bezeichnet.

Scheme, Common Lisp und viele andere Sprachen haben jedoch das sogenannte lexikalische Scoping. Zusätzlich zum Speichern der Operation (+ xy) speichern wir den Namespace an diesem bestimmten Punkt. Auf diese Weise können wir beim Nachschlagen der Werte erkennen, dass x in diesem Zusammenhang wirklich 3 ist. Dies ist ein Abschluss.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

Zusammenfassend können wir eine verknüpfte Liste verwenden, um den Status des Namespace zum Zeitpunkt der Funktionsdefinition zu speichern. Auf diese Weise können wir über einschließende Bereiche auf Variablen zugreifen und eine Variable lokal maskieren, ohne den Rest des Bereichs zu beeinflussen Programm.


Okay, dank Ihrer Antwort denke ich, dass ich endlich eine Vorstellung davon habe, worum es bei der Schließung geht. Es gibt jedoch eine große Frage: "Wir können eine verknüpfte Liste verwenden, um den Status des Namespace zum Zeitpunkt der Funktionsdefinition zu speichern und auf Variablen zuzugreifen, die sonst nicht mehr im Geltungsbereich wären." Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer

@Laser: Sorry, dieser Satz hat nicht viel Sinn gemacht, also habe ich ihn aktualisiert. Ich hoffe es macht jetzt mehr Sinn. Stellen Sie sich die verknüpfte Liste auch nicht als Implementierungsdetail vor (da sie sehr ineffizient ist), sondern als einfache Möglichkeit, sich vorzustellen, wie dies getan werden könnte.
Kyle Cronin

10

Hier ist ein Beispiel aus der Praxis, warum Closures in den Arsch treten ... Dies stammt direkt aus meinem Javascript-Code. Lassen Sie mich veranschaulichen.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

Und so würden Sie es verwenden:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Stellen Sie sich nun vor, Sie möchten, dass die Wiedergabe verzögert gestartet wird, z. B. 5 Sekunden später, nachdem dieses Code-Snippet ausgeführt wurde. Nun, das ist einfach delayund es ist ein Abschluss:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Wenn Sie delaymit 5000ms aufrufen , wird das erste Snippet ausgeführt und die übergebenen Argumente in seinem Abschluss gespeichert. Dann, 5 Sekunden später, wenn der setTimeoutRückruf erfolgt, behält der Abschluss diese Variablen weiterhin bei, sodass die ursprüngliche Funktion mit den ursprünglichen Parametern aufgerufen werden kann.
Dies ist eine Art Curry oder Funktionsdekoration.

Ohne Schließungen müssten Sie den Variablenstatus außerhalb der Funktion irgendwie beibehalten und so den Code außerhalb der Funktion mit etwas verunreinigen, das logisch dazu gehört. Die Verwendung von Verschlüssen kann die Qualität und Lesbarkeit Ihres Codes erheblich verbessern.


1
Es sollte beachtet werden, dass das Erweitern von Sprach- oder Hostobjekten im Allgemeinen als eine schlechte Sache angesehen wird, da sie Teil des globalen Namespace sind
Jon Cooke

9

Funktionen, die keine freien Variablen enthalten, werden als reine Funktionen bezeichnet.

Funktionen, die eine oder mehrere freie Variablen enthalten, werden als Abschlüsse bezeichnet.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure


Warum ist das minused? Es ist tatsächlich viel mehr "auf dem richtigen Weg" mit dieser Unterscheidung in freie Variablen und gebundene Variablen und reine / geschlossene Funktionen und unreine / offene Funktionen als die meisten anderen ahnungslosen Antworten hier: P (Diskontierung für die Verwechslung von Verschlüssen mit Funktionen geschlossen sein).
SasQ

Ich habe wirklich keine Ahnung. Aus diesem Grund ist StackOverflow zum Kotzen. Schauen Sie sich einfach die Quelle meiner Antwort an. Wer könnte damit streiten?
Soundyogi

SO saugt nicht und ich habe noch nie von dem Begriff "freie Variable" gehört
Kai

Es ist schwer, über Schließungen zu sprechen, ohne freie Variablen zu erwähnen. Schau sie dir einfach an. Standard CS Terminologie.
ComDubh

"Funktionen, die eine oder mehrere freie Variablen enthalten, werden als Closures bezeichnet" ist jedoch keine korrekte Definition - Closures sind immer erstklassige Objekte.
ComDubh

7

tl; dr

Ein Abschluss ist eine Funktion und ihr Bereich wird einer Variablen zugewiesen (oder als solche verwendet). Daher der Namensabschluss: Der Bereich und die Funktion werden wie jede andere Entität eingeschlossen und verwendet.

Ausführliche Erklärung im Wikipedia-Stil

Laut Wikipedia ist eine Schließung :

Techniken zum Implementieren der lexikalischen Namensbindung in Sprachen mit erstklassigen Funktionen.

Was bedeutet das? Schauen wir uns einige Definitionen an.

Ich werde Abschlüsse und andere verwandte Definitionen anhand dieses Beispiels erläutern:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

Erstklassige Funktionen

Grundsätzlich bedeutet dies, dass wir Funktionen wie jede andere Entität verwenden können . Wir können sie ändern, als Argumente übergeben, von Funktionen zurückgeben oder Variablen zuweisen. Technisch gesehen sind sie erstklassige Bürger , daher der Name: erstklassige Funktionen.

In dem obigen Beispiel startAtgibt eine ( anonyme ) Funktion , die Funktion zugewiesen bekommen closure1und closure2. Wie Sie sehen, behandelt JavaScript Funktionen wie alle anderen Entitäten (erstklassige Bürger).

Namensbindung

Bei der Namensbindung geht es darum herauszufinden, auf welche Daten eine Variable (Kennung) verweist . Der Umfang ist hier wirklich wichtig, da dies die Art und Weise bestimmt, wie eine Bindung aufgelöst wird.

Im obigen Beispiel:

  • Im Bereich der inneren anonymen Funktion yist gebunden3 .
  • In startAt's Umfang xist an 1oder 5(abhängig von der Schließung) gebunden .

Innerhalb des Bereichs der anonymen Funktion xist sie an keinen Wert gebunden, daher muss sie in einem oberen startAtBereich aufgelöst werden.

Lexikalisches Scoping

Wie Wikipedia sagt , ist der Umfang:

Ist der Bereich eines Computerprogramms, in dem die Bindung gültig ist: Hier kann der Name verwendet werden, um auf die Entität zu verweisen .

Es gibt zwei Techniken:

  • Lexikalisches (statisches) Scoping: Die Definition einer Variablen wird aufgelöst, indem der enthaltende Block oder die Funktion durchsucht wird. Wenn dies fehlschlägt, wird der äußere enthaltende Block nicht durchsucht, und so weiter.
  • Dynamisches Scoping: Die Aufruffunktion wird durchsucht, dann die Funktion, die diese Aufruffunktion aufgerufen hat, und so weiter, wobei der Aufrufstapel nach oben verschoben wird.

Weitere Erklärungen finden Sie in dieser Frage und in Wikipedia .

Im obigen Beispiel können wir sehen, dass JavaScript einen lexikalischen Gültigkeitsbereich hat, da beim Auflösen xdie Bindung im oberen Bereich ( startAts) basierend auf dem Quellcode (die anonyme Funktion, die nach x sucht, ist darin definiert startAt) und gesucht wird nicht basierend auf dem Aufrufstapel, der Art (dem Bereich, in dem) die Funktion aufgerufen wurde.

Einwickeln (Verschließen)

In unserem Beispiel , wenn wir anrufen startAt, wird es zurückgeben (First-Class) Funktion, die zugewiesen wird closure1und closure2somit wird ein Verschluss geschaffen, weil die übergebenen Variablen 1und 5wird innerhalb gespeichert werden startAt‚s Umfang, die mit dem zurück eingeschlossen werden anonyme Funktion. Wenn wir diese anonyme Funktion über closure1und closure2mit demselben Argument ( 3) aufrufen , wird der Wert von ysofort gefunden (da dies der Parameter dieser Funktion ist), xist jedoch nicht an den Bereich der anonymen Funktion gebunden, sodass die Auflösung in fortgesetzt wird der (lexikalisch) obere Funktionsumfang (der im Abschluss gespeichert wurde) wox festgestellt wird, dass er an entweder 1oder gebunden ist5. Jetzt wissen wir alles für die Summierung, sodass das Ergebnis zurückgegeben und dann gedruckt werden kann.

Jetzt sollten Sie die Schließungen und ihr Verhalten verstehen, was ein wesentlicher Bestandteil von JavaScript ist.

Currying

Oh, und Sie haben auch gelernt, worum es beim Currying geht: Sie verwenden Funktionen (Abschlüsse), um jedes Argument einer Operation zu übergeben, anstatt eine Funktion mit mehreren Parametern zu verwenden.


5

Closure ist eine Funktion in JavaScript, bei der eine Funktion Zugriff auf ihre eigenen Bereichsvariablen, Zugriff auf die äußeren Funktionsvariablen und Zugriff auf die globalen Variablen hat.

Closure hat auch nach Rückkehr der äußeren Funktion Zugriff auf seinen äußeren Funktionsumfang. Dies bedeutet, dass ein Abschluss Variablen und Argumente seiner äußeren Funktion auch nach Beendigung der Funktion speichern und darauf zugreifen kann.

Die innere Funktion kann auf die Variablen zugreifen, die in ihrem eigenen Bereich, dem Bereich der äußeren Funktion und dem globalen Bereich definiert sind. Die äußere Funktion kann auf die in ihrem eigenen Bereich und im globalen Bereich definierte Variable zugreifen.

Beispiel für die Schließung :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

Die Ausgabe ist 20, was die Summe der inneren Variablen der inneren Funktion, der äußeren Funktionsvariablen und des globalen Variablenwerts ist.


4

In einer normalen Situation sind Variablen an die Gültigkeitsbereichsregel gebunden: Lokale Variablen funktionieren nur innerhalb der definierten Funktion. Das Schließen ist eine Möglichkeit, diese Regel aus Bequemlichkeitsgründen vorübergehend zu brechen.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

im obigen Code lambda(|n| a_thing * n}ist der Abschluss, weil a_thingvom Lambda (einem anonymen Funktionsersteller) verwiesen wird.

Wenn Sie nun die resultierende anonyme Funktion in eine Funktionsvariable einfügen.

foo = n_times(4)

foo verstößt gegen die normale Scoping-Regel und verwendet intern 4.

foo.call(3)

gibt 12 zurück.


2

Kurz gesagt, der Funktionszeiger ist nur ein Zeiger auf eine Stelle in der Programmcodebasis (wie der Programmzähler). Während Closure = Funktionszeiger + Stapelrahmen .

.


1

• Ein Abschluss ist ein Unterprogramm und die Referenzierungsumgebung, in der es definiert wurde

- Die Referenzierungsumgebung wird benötigt, wenn das Unterprogramm von einer beliebigen Stelle im Programm aus aufgerufen werden kann

- Eine Sprache mit statischem Gültigkeitsbereich, die keine verschachtelten Unterprogramme zulässt, benötigt keine Abschlüsse

- Abschlüsse sind nur erforderlich, wenn ein Unterprogramm auf Variablen in Verschachtelungsbereichen zugreifen kann und von überall aufgerufen werden kann

- Um das Schließen zu unterstützen, muss eine Implementierung möglicherweise für einige Variablen einen unbegrenzten Umfang bereitstellen (da ein Unterprogramm möglicherweise auf eine nicht lokale Variable zugreift, die normalerweise nicht mehr aktiv ist).

Beispiel

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

Hier ist ein weiteres Beispiel aus dem wirklichen Leben und die Verwendung einer in Spielen beliebten Skriptsprache - Lua. Ich musste die Funktionsweise einer Bibliotheksfunktion leicht ändern, um ein Problem zu vermeiden, bei dem stdin nicht verfügbar war.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

Der Wert von old_dofile verschwindet, wenn dieser Codeblock seinen Gültigkeitsbereich beendet (weil er lokal ist). Der Wert wurde jedoch in einen Abschluss eingeschlossen, sodass die neu definierte Dofile-Funktion darauf zugreifen kann, oder vielmehr eine Kopie, die zusammen mit der Funktion als gespeichert wird 'upvalue'.


0

Von Lua.org :

Wenn eine Funktion in eine andere Funktion eingeschlossen geschrieben wird, hat sie über die einschließende Funktion vollen Zugriff auf lokale Variablen. Diese Funktion wird als lexikalisches Scoping bezeichnet. Das mag zwar offensichtlich klingen, ist es aber nicht. Lexikalisches Scoping und erstklassige Funktionen sind ein leistungsstarkes Konzept in einer Programmiersprache, aber nur wenige Sprachen unterstützen dieses Konzept.


0

Wenn Sie aus der Java-Welt stammen, können Sie einen Abschluss mit einer Mitgliedsfunktion einer Klasse vergleichen. Schauen Sie sich dieses Beispiel an

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

Die Funktion gist ein Abschluss: gschließt sich aan. gKann also mit einer Elementfunktion verglichen werden, akann mit einem Klassenfeld verglichen werden und die Funktion fmit einer Klasse.


0

Closures Immer wenn eine Funktion in einer anderen Funktion definiert ist, hat die innere Funktion Zugriff auf die in der äußeren Funktion deklarierten Variablen. Abschlüsse lassen sich am besten anhand von Beispielen erklären. In Listing 2-18 sehen Sie, dass die innere Funktion vom äußeren Bereich aus auf eine Variable (variableInOuterFunction) zugreifen kann. Die Variablen in der äußeren Funktion wurden durch die innere Funktion geschlossen (oder in diese gebunden). Daher der Begriff Schließung. Das Konzept an sich ist einfach genug und ziemlich intuitiv.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

Quelle: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf


0

Bitte schauen Sie unter den Code, um die Schließung genauer zu verstehen:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Was wird hier ausgegeben? 0,1,2,3,4nicht das wird 5,5,5,5,5wegen der Schließung sein

Wie wird es sich lösen? Die Antwort ist unten:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Lassen Sie mich einfach erklären, wann eine erstellte Funktion nichts passiert, bis sie im ersten Code, der 5-mal aufgerufen wurde, eine solche for-Schleife aufgerufen hat, aber nicht sofort, wenn sie aufgerufen hat, dh nach 1 Sekunde, und auch dies ist asynchron, also bevor diese for-Schleife beendet ist, und speichern Sie den Wert 5 in var i und schließlich ausführen setTimeout Funktion fünfmal und drucken5,5,5,5,5

Hier erfahren Sie, wie Sie mit IIFE lösen können, dh mit dem sofortigen Aufrufen des Funktionsausdrucks

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Weitere Informationen finden Sie im Ausführungskontext, um das Schließen zu verstehen.

  • Es gibt noch eine weitere Lösung, um dies mit let (ES6-Funktion) zu lösen, aber unter der Haube oben funktioniert die Funktion

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Weitere Erklärung:

Im Speicher, wenn für Schleife Bild ausführen, machen Sie wie folgt:

Schleife 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Schleife 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Schleife 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Schleife 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Schleife 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Hier wird i nicht ausgeführt, und nach der vollständigen Schleife hat var i den Wert 5 im Speicher gespeichert, aber sein Gültigkeitsbereich ist immer in der untergeordneten Funktion sichtbar. Wenn die Funktion also setTimeoutfünf Mal von innen nach außen ausgeführt wird , wird sie gedruckt5,5,5,5,5

Um dieses Problem zu beheben, verwenden Sie IIFE wie oben erläutert.


Danke für deine Antwort. Es wäre besser lesbar, wenn Sie Code von Erklärung trennen würden. (Zeilen, die kein Code sind, nicht
einrücken

0

Currying: Sie können eine Funktion teilweise bewerten, indem Sie nur eine Teilmenge ihrer Argumente übergeben. Bedenken Sie:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Closure: Ein Closure ist nichts anderes als der Zugriff auf eine Variable außerhalb des Funktionsumfangs. Es ist wichtig zu beachten, dass eine Funktion innerhalb einer Funktion oder eine verschachtelte Funktion kein Abschluss ist. Abschlüsse werden immer verwendet, wenn auf Variablen außerhalb des Funktionsumfangs zugegriffen werden muss.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

0

Das Schließen ist sehr einfach. Wir können es wie folgt betrachten: Closure = Funktion + seine lexikalische Umgebung

Betrachten Sie die folgende Funktion:

function init() {
    var name = “Mozilla”;
}

Was wird die Schließung im obigen Fall sein? Funktion init () und Variablen in ihrer lexikalischen Umgebung, dh Name. Closure = init () + name

Betrachten Sie eine andere Funktion:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

Was werden die Schließungen hier sein? Die innere Funktion kann auf Variablen der äußeren Funktion zugreifen. displayName () kann auf den in der übergeordneten Funktion init () deklarierten Variablennamen zugreifen. Es werden jedoch dieselben lokalen Variablen in displayName () verwendet, wenn sie vorhanden sind.

Abschluss 1: Init-Funktion + (Namensvariable + DisplayName () -Funktion) -> lexikalischer Bereich

Abschluss 2: displayName-Funktion + (Namensvariable) -> lexikalischer Bereich


0

Durch Verschlüsse wird JavaScript mit dem Status versehen.

Zustand in der Programmierung bedeutet einfach, sich an Dinge zu erinnern.

Beispiel

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

Im obigen Fall wird state in der Variablen "a" gespeichert. Wir folgen, indem wir mehrmals 1 zu "a" hinzufügen. Das können wir nur, weil wir uns an den Wert "erinnern" können. Der Staatsinhaber "a" speichert diesen Wert im Speicher.

In Programmiersprachen möchten Sie häufig den Überblick behalten, sich Informationen merken und zu einem späteren Zeitpunkt darauf zugreifen.

Dies wird in anderen Sprachen üblicherweise durch die Verwendung von Klassen erreicht. Eine Klasse verfolgt genau wie Variablen ihren Status. Und Instanzen dieser Klasse haben wiederum auch einen Status in sich. Status bedeutet einfach Informationen, die Sie später speichern und abrufen können.

Beispiel

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

Wie können wir über die "Render" -Methode auf "weight" zugreifen? Nun, danke an den Staat. Jede Instanz der Klasse Bread kann ihr eigenes Gewicht rendern, indem sie es aus dem "Zustand" liest, einem Ort im Speicher, an dem wir diese Informationen speichern können.

Jetzt ist JavaScript eine sehr einzigartige Sprache, die historisch gesehen keine Klassen hat (jetzt, aber unter der Haube gibt es nur Funktionen und Variablen), sodass Closures JavaScript die Möglichkeit bieten, sich Dinge zu merken und später darauf zuzugreifen.

Beispiel

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

count(); // # 1
count(); // # 2
count(); // # 3

Das obige Beispiel hat das Ziel erreicht, den Zustand mit einer Variablen beizubehalten. Das ist toll! Dies hat jedoch den Nachteil, dass die Variable (der "Staats" -Inhaber) jetzt verfügbar ist. Wir können es besser machen. Wir können Verschlüsse verwenden.

Beispiel

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

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

Das ist fantastisch.

Jetzt kann unsere "Zähl" -Funktion zählen. Dies ist nur möglich, weil es den Status "halten" kann. Der Zustand ist in diesem Fall die Variable "n". Diese Variable ist jetzt geschlossen. Zeitlich und räumlich geschlossen. Mit der Zeit, weil Sie es nie wieder herstellen, ändern, ihm einen Wert zuweisen oder direkt damit interagieren können. Im Weltraum, weil es geografisch in der Funktion "countGenerator" verschachtelt ist.

Warum ist das fantastisch? Denn ohne ein anderes ausgeklügeltes und kompliziertes Werkzeug (z. B. Klassen, Methoden, Instanzen usw.) können wir 1. die Kontrolle aus der Ferne verbergen

Wir verbergen den Zustand, die Variable "n", was ihn zu einer privaten Variablen macht! Wir haben auch eine API erstellt, die diese Variable auf vordefinierte Weise steuern kann. Insbesondere können wir die API wie folgt aufrufen: "count ()" und das addiert 1 zu "n" aus einer "Entfernung". In keiner Weise, Form oder Gestalt kann jemals jemand über die API auf "n" zugreifen.

JavaScript ist in seiner Einfachheit wirklich erstaunlich.

Verschlüsse sind ein großer Teil dessen, warum dies so ist.


0

Ein einfaches Beispiel in Groovy als Referenz:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.