Ein Verschluss ist eine Paarung von:
- Eine Funktion und
- Ein Verweis auf den äußeren Bereich dieser Funktion (lexikalische Umgebung)
Eine lexikalische Umgebung ist Teil jedes Ausführungskontextes (Stackframe) und ist eine Abbildung zwischen Bezeichnern (d.h. lokalen Variablennamen) und Werten.
Jede Funktion in JavaScript unterhält einen Verweis auf ihre äußere lexikalische Umgebung. Dieser Verweis wird verwendet, um den Ausführungskontext zu konfigurieren, der beim Aufrufen einer Funktion erstellt wird. Dieser Verweis ermöglicht es dem Code innerhalb der Funktion, außerhalb der Funktion deklarierte Variablen zu "sehen", unabhängig davon, wann und wo die Funktion aufgerufen wird.
Wurde eine Funktion von einer Funktion aufgerufen, die wiederum von einer anderen Funktion aufgerufen wurde, so entsteht eine Kette von Verweisen auf äußere lexikalische Umgebungen. Diese Kette wird als Scope-Kette bezeichnet.
Im folgenden Code, inner
bildet einen Abschluss mit der lexikalischen Umgebung des Ausführungskontextes, der entsteht, wenn foo
aufgerufen wird, zu Ende gehend variabel secret
:
function foo() {
const secret = Math.trunc(Math.random() * 100)
return function inner() {
console.log(`The secret number is ${secret}.`)
}
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`
Mit anderen Worten: In JavaScript enthalten Funktionen einen Verweis auf eine private "Box of State", auf die nur sie (und alle anderen in derselben lexikalischen Umgebung deklarierten Funktionen) Zugriff haben. Diese Zustandsbox ist für den Aufrufer der Funktion unsichtbar und bietet einen hervorragenden Mechanismus zum Verstecken und zur Kapselung von Daten.
Und denken Sie daran: Funktionen in JavaScript können wie Variablen weitergegeben werden (Funktionen erster Klasse), d. h. diese Paare von Funktionen und Zuständen können in Ihrem Programm weitergegeben werden: ähnlich wie Sie eine Instanz einer Klasse in C++ weitergeben könnten.
Gäbe es in JavaScript keine Abschlüsse, müssten mehr Zustände zwischen Funktionen übergeben werden ausdrücklich Dadurch werden die Parameterlisten länger und der Code verrauscht.
Wenn Sie also wollen, dass eine Funktion immer Zugriff auf einen privaten Teil des Zustands hat, können Sie einen Abschluss verwenden.
...und häufig sind wir faire den Zustand mit einer Funktion verknüpfen wollen. Wenn Sie zum Beispiel in Java oder C++ einer Klasse eine private Instanzvariable und eine Methode hinzufügen, verknüpfen Sie den Zustand mit einer Funktion.
In C und den meisten anderen gängigen Sprachen ist nach der Rückkehr einer Funktion kein Zugriff mehr auf alle lokalen Variablen möglich, da der Stack-Frame zerstört wird. Wenn Sie in JavaScript eine Funktion innerhalb einer anderen Funktion deklarieren, können die lokalen Variablen der äußeren Funktion auch nach der Rückkehr von dieser zugänglich bleiben. Auf diese Weise wird im obigen Code, secret
bleibt für das Funktionsobjekt verfügbar inner
, 後 es wurde zurückgegeben von foo
.
Verwendungszwecke von Verschlüssen
Closures sind immer dann nützlich, wenn Sie einen privaten Zustand in Verbindung mit einer Funktion benötigen. Dies ist ein sehr häufiges Szenario - und denken Sie daran: JavaScript verfügte bis 2015 nicht über eine Klassensyntax und hat immer noch keine Syntax für private Felder. Closures erfüllen diesen Bedarf.
Private Instanzvariablen
In dem folgenden Code wird die Funktion toString
schließt die Details des Fahrzeugs.
function Car(manufacturer, model, year, color) {
return {
toString() {
return `${manufacturer} ${model} (${year}, ${color})`
}
}
}
const car = new Car('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver')
console.log(car.toString())
Funktionale Programmierung
In dem folgenden Code wird die Funktion inner
schließt sich über beide fn
y args
.
function curry(fn) {
const args = []
return function inner(arg) {
if(args.length === fn.length) return fn(...args)
args.push(arg)
return inner
}
}
function add(a, b) {
return a + b
}
const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5
Ereignisorientiertes Programmieren
Im folgenden Code wird die Funktion onClick
schließt über Variable BACKGROUND_COLOR
.
const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200, 200, 242, 1)'
function onClick() {
$('body').style.background = BACKGROUND_COLOR
}
$('button').addEventListener('click', onClick)
<button>Set background color</button>
Modularisierung
Im folgenden Beispiel sind alle Implementierungsdetails in einem unmittelbar ausgeführten Funktionsausdruck verborgen. Die Funktionen tick
y toString
den privaten Zustand und die Funktionen zu schließen, die sie für ihre Arbeit benötigen. Closures haben es uns ermöglicht, unseren Code zu modularisieren und zu kapseln.
let namespace = {};
(function foo(n) {
let numbers = []
function format(n) {
return Math.trunc(n)
}
function tick() {
numbers.push(Math.random() * 100)
}
function toString() {
return numbers.map(format)
}
n.counter = {
tick,
toString
}
}(namespace))
const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())
Beispiele
Beispiel 1
Dieses Beispiel zeigt, dass die lokalen Variablen nicht in die Schließung kopiert werden: die Schließung behält einen Verweis auf die ursprünglichen Variablen bei selbst . Es ist, als ob der Stack-Frame auch nach Beendigung der äußeren Funktion im Speicher erhalten bleibt.
function foo() {
let x = 42
let inner = () => console.log(x)
x = x + 1
return inner
}
foo()() // logs 43
Beispiel 2
Der folgende Code enthält drei Methoden log
, increment
et update
alle über dieselbe lexikalische Umgebung schließen.
Und jedes Mal createObject
aufgerufen wird, wird ein neuer Ausführungskontext (Stack-Frame) erstellt und eine völlig neue Variable x
und eine neue Reihe von Funktionen ( log
usw.) erstellt, die über diese neue Variable schließen.
function createObject() {
let x = 42;
return {
log() { console.log(x) },
increment() { x++ },
update(value) { x = value }
}
}
const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42
Beispiel 3
Wenn Sie Variablen verwenden, die mit var
Achten Sie darauf, dass Sie wissen, welche Variable Sie schließen. Variablen, die mit var
hochgezogen werden. In modernem JavaScript ist dieses Problem dank der Einführung von let
y const
.
Im folgenden Code wird jedes Mal, wenn die Schleife durchlaufen wird, eine neue Funktion inner
erstellt, das sich über i
. Aber weil var i
aus der Schleife herausgezogen wird, schließen alle diese inneren Funktionen über dieselbe Variable, was bedeutet, dass der Endwert von i
(3) wird dreimal gedruckt.
function foo() {
var result = []
for (var i = 0; i < 3; i++) {
result.push(function inner() { console.log(i) } )
}
return result
}
const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
result[i]()
}
Letzte Punkte:
- Wann immer eine Funktion in JavaScript deklariert wird, wird ein Abschluss erstellt.
- Rücksendung einer
function
innerhalb einer anderen Funktion ist das klassische Beispiel für einen Abschluss, da der Zustand innerhalb der äußeren Funktion implizit für die zurückgegebene innere Funktion verfügbar ist, selbst nachdem die äußere Funktion ihre Ausführung beendet hat.
- Wann immer Sie
eval()
innerhalb einer Funktion wird ein Abschluss verwendet. Der Text, den Sie eval
kann auf lokale Variablen der Funktion verweisen, und im nicht-strikten Modus können Sie sogar neue lokale Variablen erstellen, indem Sie eval('var foo = …')
.
- Wenn Sie
new Function(…)
(die Funktionskonstrukteur ) innerhalb einer Funktion, schließt sie sich nicht über ihre lexikalische Umgebung ab: Sie schließt sich stattdessen über den globalen Kontext ab. Die neue Funktion kann nicht auf die lokalen Variablen der äußeren Funktion verweisen.
- Ein Abschluss in JavaScript ist wie eine Referenz ( NO eine Kopie) an den Bereich am Punkt der Funktionsdeklaration, der wiederum einen Verweis auf seinen äußeren Bereich behält, und so weiter, bis hin zum globalen Objekt am Anfang der Bereichskette.
- Bei der Deklaration einer Funktion wird eine Closure erstellt, die beim Aufruf der Funktion zur Konfiguration des Ausführungskontextes verwendet wird.
- Bei jedem Funktionsaufruf wird ein neuer Satz lokaler Variablen erstellt.
Links