Ein Freund von mir und ich diskutieren derzeit, was eine Schließung in JS ist und was nicht. Wir wollen nur sicherstellen, dass wir es wirklich richtig verstehen.
Nehmen wir dieses Beispiel. Wir haben eine Zählschleife und möchten die Zählervariable verzögert auf der Konsole drucken. Daher verwenden wir setTimeout
und Verschlüsse , um den Wert der Zählervariablen zu erfassen und sicherzustellen, dass sie nicht das N-fache des Werts N druckt.
Die falsche Lösung ohne Verschlüsse oder irgendetwas in der Nähe von Verschlüssen wäre:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
das druckt natürlich das 10-fache des Wertes von i
nach der Schleife, nämlich 10.
Also war sein Versuch:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
wie erwartet 0 bis 9 drucken.
Ich sagte ihm, dass er keinen Verschluss benutzt , um zu fangen i
, aber er besteht darauf, dass er es ist. Ich habe bewiesen, dass er keine Verschlüsse verwendet, indem er den for-Schleifenkörper in einen anderen gelegt hat setTimeout
(indem er seine anonyme Funktion an übergeben hat setTimeout
) und erneut 10 mal 10 gedruckt hat. Gleiches gilt, wenn ich seine Funktion in a speichere var
und sie nach der Schleife ausführe und auch 10 mal 10 drucke. Mein Argument ist also, dass er den Wert von nicht wirklich erfassti
und seine Version nicht zu einem Abschluss macht.
Mein Versuch war:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Also ich capture i
(benannt i2
innerhalb des Verschlusses), aber jetzt habe ich zurückgeben eine andere Funktion und diese um passieren. In meinem Fall erfasst die an setTimeout übergebene Funktion wirklich i
.
Wer benutzt nun Verschlüsse und wer nicht?
Beachten Sie, dass beide Lösungen verzögert 0 bis 9 auf der Konsole drucken, sodass sie das ursprüngliche Problem lösen. Wir möchten jedoch verstehen, welche dieser beiden Lösungen Verschlüsse verwendet , um dies zu erreichen.
quelle
Antworten:
Anmerkung des Herausgebers: Alle Funktionen in JavaScript sind Schließungen, wie in diesem Beitrag erläutert . Wir sind jedoch nur daran interessiert, eine Teilmenge dieser Funktionen zu identifizieren, die aus theoretischer Sicht interessant sind . Sofern nicht anders angegeben, bezieht sich jede Bezugnahme auf das Wort Schließen auf diese Teilmenge von Funktionen.
Eine einfache Erklärung für Schließungen:
Verwenden wir dies nun, um herauszufinden, wer Verschlüsse verwendet und wer nicht (zur Erklärung habe ich die Funktionen benannt):
Fall 1: Das Programm Ihres Freundes
Im obigen Programm gibt es zwei Funktionen:
f
undg
. Mal sehen, ob es sich um Verschlüsse handelt:Für
f
:i2
ist eine lokale Variable.i
ist eine freie Variable.setTimeout
ist eine freie Variable.g
ist eine lokale Variable.console
ist eine freie Variable.i
ist an den globalen Geltungsbereich gebunden .setTimeout
ist an den globalen Geltungsbereich gebunden .console
ist an den globalen Geltungsbereich gebunden .i
wird nicht geschlossen über durchf
.setTimeout
wird nicht geschlossen über durchf
.console
wird nicht geschlossen über durchf
.Somit ist die Funktion
f
kein Abschluss.Für
g
:console
ist eine freie Variable.i2
ist eine freie Variable.console
ist an den globalen Geltungsbereich gebunden .i2
ist an den Geltungsbereich von gebundenf
.setTimeout
.console
wird nicht geschlossen über durchg
.i2
wird geschlossen über durchg
.Somit ist die Funktion
g
ist ein Verschluss für die freie Variablei2
(der ein für aufzuwerten istg
) , wenn es referenziert von innensetTimeout
.Schlecht für dich: Dein Freund benutzt einen Verschluss. Die innere Funktion ist ein Verschluss.
Fall 2: Ihr Programm
Im obigen Programm gibt es zwei Funktionen:
f
undg
. Mal sehen, ob es sich um Verschlüsse handelt:Für
f
:i2
ist eine lokale Variable.g
ist eine lokale Variable.console
ist eine freie Variable.console
ist an den globalen Geltungsbereich gebunden .console
wird nicht geschlossen über durchf
.Somit ist die Funktion
f
kein Abschluss.Für
g
:console
ist eine freie Variable.i2
ist eine freie Variable.console
ist an den globalen Geltungsbereich gebunden .i2
ist an den Geltungsbereich von gebundenf
.setTimeout
.console
wird nicht geschlossen über durchg
.i2
wird geschlossen über durchg
.Somit ist die Funktion
g
ist ein Verschluss für die freie Variablei2
(der ein für aufzuwerten istg
) , wenn es referenziert von innensetTimeout
.Gut für Sie: Sie verwenden einen Verschluss. Die innere Funktion ist ein Verschluss.
Sie und Ihr Freund verwenden also Verschlüsse. Hört auf zu streiten. Ich hoffe, ich habe das Konzept der Verschlüsse geklärt und wie ich sie für Sie beide identifizieren kann.
Bearbeiten: Eine einfache Erklärung, warum alle Funktionen geschlossen sind (Credits @Peter):
Betrachten wir zunächst das folgende Programm (es ist die Steuerung ):
lexicalScope
undregularFunction
keine Verschlüsse aus der obigen Definition sind .message
benachrichtigt werden, daregularFunction
es sich nicht um einen Abschluss handelt (dh es hat Zugriff auf alle Variablen in seinem übergeordneten Bereich - einschließlichmessage
).message
tatsächlich alarmiert ist.Als nächstes betrachten wir das folgende Programm (es ist die Alternative ):
closureFunction
ein Abschluss aus der obigen Definition ist .message
nicht gewarnt werden , weilclosureFunction
ein Verschluss (dh es hat nur Zugriff auf alle seine nicht-lokalen Variablen in der Zeit wird die Funktion erstellt ( siehe diese Antwort ) - dies schließt nichtmessage
).message
es tatsächlich alarmiert wird.Was schließen wir daraus?
quelle
g
im Bereich von ausgeführtsetTimeout
wird, in Fall 2 sagen Sie, dass diesf
im globalen Bereich ausgeführt wird. Sie befinden sich beide in setTimeout. Was ist also der Unterschied?Nach der
closure
Definition:Sie verwenden,
closure
wenn Sie eine Funktion definieren, die eine Variable verwendet, die außerhalb der Funktion definiert ist. (Wir nennen die Variable eine freie Variable ).Sie alle verwenden
closure
(auch im 1. Beispiel).quelle
i2
die außerhalb definiert ist.Kurz gesagt, mit Javascript Closures kann eine Funktion auf eine Variable zugreifen , die in einer lexikalisch übergeordneten Funktion deklariert ist .
Sehen wir uns eine detailliertere Erklärung an. Um Abschlüsse zu verstehen, ist es wichtig zu verstehen, wie JavaScript Variablen abdeckt.
Geltungsbereich
In JavaScript werden Bereiche mit Funktionen definiert. Jede Funktion definiert einen neuen Bereich.
Betrachten Sie das folgende Beispiel;
Aufrufen von f-Drucken
Betrachten wir nun den Fall, dass eine Funktion
g
in einer anderen Funktion definiert istf
.Wir werden
f
das lexikalische Elternteil von nenneng
. Wie zuvor erklärt, haben wir jetzt 2 Bereiche; den Umfangf
und den Umfangg
.Ein Bereich liegt jedoch "innerhalb" des anderen Bereichs. Ist der Bereich der untergeordneten Funktion Teil des Bereichs der übergeordneten Funktion? Was passiert mit den Variablen, die im Bereich der übergeordneten Funktion deklariert sind? Kann ich über den Bereich der untergeordneten Funktion auf sie zugreifen? Genau hier setzen Verschlüsse an.
Verschlüsse
In JavaScript kann die Funktion
g
nicht nur auf Variablen zugreifen, die im Gültigkeitsbereich deklariert sind,g
sondern auch auf Variablen, die im Gültigkeitsbereich der übergeordneten Funktion deklariert sindf
.Betrachten Sie Folgendes;
Aufrufen von f-Drucken
Schauen wir uns die Linie an
console.log(foo);
. Zu diesem Zeitpunkt befinden wir uns im Gültigkeitsbereichg
und versuchen, auf diefoo
im Gültigkeitsbereich deklarierte Variable zuzugreifenf
. Wie bereits erwähnt, können wir auf jede Variable zugreifen, die in einer lexikalischen übergeordneten Funktion deklariert ist, was hier der Fall ist.g
ist das lexikalische Elternteil vonf
. Daherhello
wird gedruckt.Schauen wir uns jetzt die Linie an
console.log(bar);
. Zu diesem Zeitpunkt befinden wir uns im Gültigkeitsbereichf
und versuchen, auf diebar
im Gültigkeitsbereich deklarierte Variable zuzugreifeng
.bar
wird im aktuellen Bereich nicht deklariert und die Funktiong
ist nicht das übergeordnete Element vonf
, ist daherbar
undefiniertTatsächlich können wir auch auf die Variablen zugreifen, die im Rahmen einer lexikalischen "Grand Parent" -Funktion deklariert wurden. Daher, wenn
h
innerhalb der Funktion eine Funktion definiert wäreg
dann
h
im Rahmen der Funktion deklariert alle Variablen zuzugreifen wäre in der Lageh
,g
undf
. Dies geschieht mit Verschlüssen . In JavaScript- Schließungen können wir auf alle Variablen zugreifen, die in der lexikalischen übergeordneten Funktion, in der lexikalischen Großelternfunktion, in der lexikalischen Großelternfunktion usw. deklariert sind. Dies kann als Bereichskette angesehen werden .scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
bis zur letzten übergeordneten Funktion, die kein lexikalisches übergeordnetes Element hat.Das Fensterobjekt
Tatsächlich stoppt die Kette nicht bei der letzten übergeordneten Funktion. Es gibt noch einen besonderen Bereich; der globale Umfang . Jede Variable, die nicht in einer Funktion deklariert ist, gilt als im globalen Bereich deklariert. Der globale Geltungsbereich hat zwei Besonderheiten;
window
Objekts.Daher gibt es genau zwei Möglichkeiten, eine Variable
foo
im globalen Bereich zu deklarieren . entweder indem Sie es nicht in einer Funktion deklarieren oder indem Sie die Eigenschaftfoo
des Fensterobjekts festlegen.Bei beiden Versuchen werden Verschlüsse verwendet
Nachdem Sie eine detailliertere Erklärung gelesen haben, kann es offensichtlich sein, dass beide Lösungen Verschlüsse verwenden. Aber um sicher zu gehen, machen wir einen Beweis.
Lassen Sie uns eine neue Programmiersprache erstellen. JavaScript-No-Closure. Wie der Name schon sagt, ist JavaScript-No-Closure mit JavaScript identisch, außer dass Closures nicht unterstützt werden.
Mit anderen Worten;
Okay, mal sehen, was mit der ersten Lösung mit JavaScript-No-Closure passiert.
Daher wird dies
undefined
10 Mal in JavaScript-No-Closure gedruckt .Daher verwendet die erste Lösung den Verschluss.
Schauen wir uns die zweite Lösung an.
Daher wird dies
undefined
10 Mal in JavaScript-No-Closure gedruckt .Beide Lösungen verwenden Verschlüsse.
Bearbeiten: Es wird davon ausgegangen, dass diese 3 Codefragmente nicht im globalen Bereich definiert sind. Andernfalls wären die Variablen
foo
undi
an daswindow
Objekt gebunden und daher über daswindow
Objekt sowohl in JavaScript als auch in JavaScript-No-Closure zugänglich .quelle
i
undefiniert sein? Sie beziehen sich nur auf den übergeordneten Bereich, der weiterhin gültig ist, wenn keine Abschlüsse vorhanden waren.i2
,i
zu dem Sie Ihre Funktion definieren. Dies machti
KEINE freie Variable. Trotzdem betrachten wir Ihre Funktion als Abschluss, aber ohne freie Variable ist das der Punkt.Ich war noch nie glücklich darüber, wie jemand dies erklärt.
Der Schlüssel zum Verständnis von Schließungen besteht darin, zu verstehen, wie JS ohne Schließungen aussehen würde.
Ohne Verschlüsse würde dies einen Fehler auslösen
Sobald OuterFunc in einer imaginären Version von JavaScript mit deaktiviertem Abschluss zurückgekehrt ist, wird der Verweis auf OuterVar als Müll gesammelt und ist nicht mehr vorhanden, sodass die innere Funktion darauf verweisen kann.
Closures sind im Wesentlichen die speziellen Regeln, die diese Variablen aktivieren und ermöglichen, wenn eine innere Funktion auf die Variablen einer äußeren Funktion verweist. Bei Schließungen bleiben die referenzierten Variablen auch nach Ausführung der äußeren Funktion oder "geschlossen" erhalten, wenn dies Ihnen hilft, sich an den Punkt zu erinnern.
Selbst bei Verschlüssen funktioniert der Lebenszyklus lokaler Vars in einer Funktion ohne innere Funktionen, die auf die lokalen Verweise verweisen, genauso wie in einer Version ohne Verschluss. Wenn die Funktion beendet ist, erhalten die Einheimischen Müll gesammelt.
Sobald Sie in einer inneren Funktion einen Verweis auf eine äußere Variable haben, ist es jedoch so, als würde ein Türpfosten der Speicherbereinigung für die referenzierten Variablen im Wege stehen.
Eine vielleicht genauere Art, Verschlüsse zu betrachten, besteht darin, dass die innere Funktion im Grunde genommen den inneren Bereich als ihre eigene Bereichsfernung verwendet.
Der Kontext, auf den verwiesen wird, ist jedoch tatsächlich persistent und nicht wie ein Schnappschuss. Durch wiederholtes Auslösen einer zurückgegebenen inneren Funktion, die die lokale Variable einer äußeren Funktion weiter erhöht und protokolliert, werden immer höhere Werte angezeigt.
quelle
Sie verwenden beide Verschlüsse.
Ich gehe hier mit der Wikipedia-Definition :
Der Versuch Ihres Freundes verwendet eindeutig die Variable
i
, die nicht lokal ist, indem er ihren Wert nimmt und eine Kopie erstellt, um sie im lokalen zu speicherni2
.Ihr eigener Versuch
i
wird als Argument an eine anonyme Funktion übergeben (die sich an der Aufrufstelle befindet). Dies ist bisher kein Abschluss, aber dann gibt diese Funktion eine andere Funktion zurück, die auf dieselbe verweisti2
. Da es sich bei der inneren anonymen Funktioni2
nicht um eine lokale handelt, wird ein Abschluss erstellt.quelle
i
nachi2
, definiert dann eine Logik und führt diese Funktion aus. Wenn ich es nicht sofort ausführen würde, sondern es in einer Variablen speichern und nach der Schleife ausführen würde, würde es 10 drucken, nicht wahr? Also hat es mich nicht erfasst.i
ganz gut erfasst . Das Verhalten, das Sie beschreiben, ist kein Ergebnis von Schließung oder Nichtschließung. Dies ist darauf zurückzuführen, dass die geschlossene Variable in der Zwischenzeit geändert wurde. Sie tun dasselbe mit einer anderen Syntax, indem Sie sofort eine Funktion aufrufen undi
als Argument übergeben (das den aktuellen Wert sofort kopiert). Wenn Sie Ihre eigenensetTimeout
in eine anderesetTimeout
stecken, wird dasselbe passieren.Sie und Ihr Freund verwenden beide Verschlüsse:
In der Code-Funktion Ihres Freundes
function(){ console.log(i2); }
definiert innerhalb der Schließung der anonymen Funktionfunction(){ var i2 = i; ...
und kann lokale Variable lesen / schreibeni2
.In Ihrem Code wird die Funktion
function(){ console.log(i2); }
innerhalb des Funktionsabschlusses definiertfunction(i2){ return ...
und kann lokal wertvoll lesen / schreibeni2
(in diesem Fall als Parameter deklariert).In beiden Fällen wird die Funktion
function(){ console.log(i2); }
dann übergebensetTimeout
.Ein weiteres Äquivalent (jedoch mit geringerer Speicherauslastung) ist:
quelle
Schließung
Ein Abschluss ist keine Funktion und kein Ausdruck. Es muss als eine Art "Momentaufnahme" der verwendeten Variablen außerhalb des Funktionsumfangs und innerhalb der Funktion betrachtet werden. Grammatisch sollte man sagen: "Schließen Sie die Variablen".
Nochmals mit anderen Worten: Ein Abschluss ist eine Kopie des relevanten Kontextes von Variablen, von denen die Funktion abhängt.
Noch einmal (naiv): Ein Abschluss hat Zugriff auf Variablen, die nicht als Parameter übergeben werden.
Beachten Sie, dass diese Funktionskonzepte stark von der von Ihnen verwendeten Programmiersprache / -umgebung abhängen. In JavaScript hängt der Abschluss vom lexikalischen Umfang ab (was in den meisten C-Sprachen der Fall ist).
Wenn Sie also eine Funktion zurückgeben, wird meistens eine anonyme / unbenannte Funktion zurückgegeben. Wenn die Funktion auf Variablen zugreift, die nicht als Parameter übergeben wurden und innerhalb ihres (lexikalischen) Bereichs liegen, wurde ein Abschluss vorgenommen.
Also, zu Ihren Beispielen:
Alle benutzen Verschlüsse. Verwechseln Sie den Ausführungspunkt nicht mit Schließungen. Wenn der 'Schnappschuss' der Schließungen im falschen Moment gemacht wird, können die Werte unerwartet sein, aber es wird sicherlich eine Schließung gemacht!
quelle
Schauen wir uns beide Möglichkeiten an:
Deklariert und führt sofort eine anonyme Funktion aus, die
setTimeout()
in ihrem eigenen Kontext ausgeführt wird. Der aktuelle Wert voni
wird beibehalten, indem zuerst eine Kopie erstellt wirdi2
. es funktioniert wegen der sofortigen Ausführung.Deklariert einen Ausführungskontext für die innere Funktion, in dem der aktuelle Wert von
i
beibehalten wirdi2
. Dieser Ansatz verwendet auch die sofortige Ausführung, um den Wert zu erhalten.Wichtig
Es sollte erwähnt werden, dass die Laufsemantik zwischen beiden Ansätzen NICHT gleich ist. Ihre innere Funktion wird übergeben,
setTimeout()
während sich seine innere FunktionsetTimeout()
selbst nennt .Das Einwickeln beider Codes in einen anderen
setTimeout()
beweist nicht, dass nur der zweite Ansatz Verschlüsse verwendet. Es gibt einfach nicht dasselbe.Fazit
Bei beiden Methoden werden Verschlüsse verwendet, daher kommt es auf den persönlichen Geschmack an. Der zweite Ansatz ist einfacher zu "bewegen" oder zu verallgemeinern.
quelle
setTimeout()
?i
geändert werden kann, ohne dass dies Auswirkungen darauf hat, was die Funktion drucken soll, unabhängig davon, wo oder wann wir sie ausführen.()
und übergeben Sie eine Funktion. Sie sehen das 10-fache der Ausgabe10
.()
ist es genau das, was seinen Code funktioniert, genau wie Ihren(i)
; Sie haben nicht nur seinen Code verpackt, sondern Änderungen daran vorgenommen. Daher können Sie keinen gültigen Vergleich mehr durchführen.Ich habe dies vor einiger Zeit geschrieben, um mich daran zu erinnern, was ein Abschluss ist und wie er in JS funktioniert.
Ein Abschluss ist eine Funktion, die beim Aufruf den Bereich verwendet, in dem sie deklariert wurde, nicht den Bereich, in dem sie aufgerufen wurde. In JavaScript verhalten sich alle Funktionen so. Variablenwerte in einem Bereich bleiben bestehen, solange eine Funktion vorhanden ist, die noch auf sie verweist. Die Ausnahme von der Regel ist 'this', die sich auf das Objekt bezieht, in dem sich die Funktion befindet, wenn sie aufgerufen wird.
quelle
Nach eingehender Prüfung sieht es so aus, als würden Sie beide einen Verschluss verwenden.
Im Fall Ihrer Freunde
i
wird in der anonymen Funktion 1 undi2
in der anonymen Funktion 2 zugegriffen, in derconsole.log
die vorhanden ist.In Ihrem Fall greifen Sie auf eine
i2
anonyme Funktion zu, dieconsole.log
vorhanden ist. Fügen Siedebugger;
vorconsole.log
und in den Chrome-Entwicklertools unter "Bereichsvariablen" eine Anweisung hinzu, die angibt, unter welchem Bereich die Variable liegt.quelle
Folgendes berücksichtigen. Dadurch wird eine Funktion erstellt und neu erstellt
f
, die geschlossen wirdi
, aber andere!:während das Folgende "eine" Funktion "selbst" schließt
(selbst! das Snippet danach verwendet einen einzelnen Referenten
f
)oder genauer gesagt:
NB. Die letzte Definition von
f
isfunction(){ console.log(9) }
before0
wird gedruckt.Vorbehalt! Das Verschlusskonzept kann eine zwingende Ablenkung vom Wesen der elementaren Programmierung sein:
x-refs.:
Wie funktionieren JavaScript-Schließungen?
Erläuterungen zu Javascript-Verschlüssen
Erfordert ein (JS) -Verschluss eine Funktion innerhalb einer Funktion
? Wie verstehe ich Verschlüsse in Javascript?
Lokale und globale Verwirrung von Javascript-Variablen
quelle
Run' only was desired - not sure how to remove the
Copy`Ich möchte mein Beispiel und eine Erklärung zu Schließungen mitteilen. Ich habe ein Python-Beispiel und zwei Abbildungen gemacht, um die Stapelzustände zu demonstrieren.
Die Ausgabe dieses Codes wäre wie folgt:
Hier sind zwei Abbildungen, die Stapel und den am Funktionsobjekt angebrachten Verschluss zeigen.
wenn die Funktion vom Hersteller zurückgegeben wird
wenn die Funktion später aufgerufen wird
Wenn die Funktion über einen Parameter oder eine nichtlokale Variable aufgerufen wird, benötigt der Code lokale Variablenbindungen wie margin_top, padding sowie a, b, n. Um sicherzustellen, dass der Funktionscode funktioniert, sollte auf den Stapelrahmen der Maker-Funktion zugegriffen werden können, der vor langer Zeit entfernt wurde. Dieser wird in dem Abschluss gesichert, den wir zusammen mit dem Funktionsnachrichtenobjekt finden.
quelle
delete
Link unter der Antwort.