Die Callbacks, Event-Handler, Funktionen höherer Ordnung können dank Closures auf Variablen des äußeren Bereichs zugreifen. Closures sind wichtig in der funktionalen Programmierung und werden oft im JavaScript-Coding-Interview abgefragt.
Obwohl sie überall verwendet werden, sind Closures schwer zu begreifen. Wenn Sie Ihren „Aha!“-Moment beim Verständnis von Closures noch nicht hatten, dann ist dieser Beitrag für Sie.
Ich beginne mit den grundlegenden Begriffen: Scope und lexikalischer Scope. Nachdem Sie die Grundlagen verstanden haben, brauchen Sie nur noch einen Schritt, um Closures endgültig zu verstehen.
Bevor Sie beginnen, schlage ich vor, dass Sie dem Drang widerstehen, die Abschnitte zum Geltungsbereich und zum lexikalischen Geltungsbereich zu überspringen. Diese Konzepte sind entscheidend für Closures, und wenn Sie sie gut verstanden haben, wird die Idee von Closures selbstverständlich.
1. der Geltungsbereich
Wenn Sie eine Variable definieren, wollen Sie, dass sie innerhalb bestimmter Grenzen zugänglich ist. Eine result
-Variable macht z. B. Sinn, wenn sie innerhalb einer calculate()
-Funktion existiert, als internes Detail. Außerhalb des calculate()
ist die result
-Variable nutzlos.
Die Zugänglichkeit von Variablen wird über den Scope verwaltet. Innerhalb ihres Geltungsbereichs können Sie frei auf die definierte Variable zugreifen. Aber außerhalb dieses Bereichs ist die Variable unzugänglich.
In JavaScript wird ein Scope durch eine Funktion oder einen Codeblock erzeugt.
Sehen wir uns an, wie der Scope die Verfügbarkeit einer Variablen count
beeinflusst. Diese Variable gehört zu einem Scope, der von der Funktion foo()
erzeugt wurde:
function foo() { // The function scope let count = 0; console.log(count); // logs 0}foo();console.log(count); // ReferenceError: count is not defined
count
ist im Scope von foo()
frei zugänglich.
Außerhalb des Bereichs von foo()
ist count
jedoch unzugänglich. Wenn Sie trotzdem versuchen, von außen auf count
zuzugreifen, wirft JavaScript ReferenceError: count is not defined
.
In JavaScript sagt der Scope: Wenn Sie eine Variable innerhalb einer Funktion oder eines Codeblocks definiert haben, dann können Sie diese Variable nur innerhalb dieser Funktion oder dieses Codeblocks verwenden. Das obige Beispiel demonstriert dieses Verhalten.
Nun sehen wir uns eine allgemeine Formulierung an:
Der Scope ist eine Raumrichtlinie, die die Zugänglichkeit von Variablen regelt.
Eine Eigenschaft ergibt sich sofort: Der Scope isoliert Variablen. Das ist toll, denn verschiedene Scopes können Variablen mit demselben Namen haben.
Sie können gängige Variablennamen (count
index
current
value
, usw.) in verschiedenen Scopes ohne Kollisionen wiederverwenden.
foo()
und bar()
Funktionsbereiche haben eigene, aber gleichnamige, Variablen count
:
function foo() { // "foo" function scope let count = 0; console.log(count); // logs 0}function bar() { // "bar" function scope let count = 1; console.log(count); // logs 1}foo();bar();
count
Variablen aus foo()
und bar()
Funktionsumfängen kollidieren nicht.
2. Verschachtelung von Scopes
Lassen Sie uns ein wenig mehr mit Scopes spielen und einen Scope in einen anderen setzen.
Die Funktion innerFunc()
ist innerhalb einer äußeren Funktion outerFunc()
verschachtelt.
Wie würden die 2 Funktions-Scopes miteinander interagieren? Kann ich auf die Variable outerVar
von outerFunc()
aus dem innerFunc()
-Scope heraus zugreifen?
Lassen Sie uns das im Beispiel ausprobieren:
function outerFunc() { // the outer scope let outerVar = 'I am outside!'; function innerFunc() { // the inner scope console.log(outerVar); // => logs "I am outside!" } innerFunc();}outerFunc();
In der Tat ist die Variable outerVar
innerhalb des innerFunc()
-Bereichs zugänglich. Die Variablen des äußeren Bereichs sind innerhalb des inneren Bereichs zugreifbar.
Nun wissen Sie 2 interessante Dinge:
- Scopes können verschachtelt werden
- Die Variablen des äußeren Scopes sind innerhalb des inneren Scopes zugreifbar
3. Der lexikalische Scope
Wie versteht JavaScript, dass outerVar
innerhalb von innerFunc()
der Variable outerVar
von outerFunc()
entspricht?
Das liegt daran, dass JavaScript einen Scoping-Mechanismus namens lexical scoping (oder static scoping) implementiert. Lexikalisches Scoping bedeutet, dass die Zugänglichkeit von Variablen durch die Position der Variablen im Quellcode innerhalb der verschachtelten Scopes bestimmt wird.
Vereinfacht bedeutet das lexikalische Scoping, dass man innerhalb des inneren Scopes auf Variablen seiner äußeren Scopes zugreifen kann.
Es wird lexikalisch (oder statisch) genannt, weil die Engine (zur Lexing-Zeit) die Verschachtelung der Scopes nur durch einen Blick auf den JavaScript-Quellcode bestimmt, ohne ihn auszuführen.
Hier ist, wie die Engine den vorherigen Codeschnipsel versteht:
- Ich sehe, Sie definieren eine Funktion
outerFunc()
, die eine VariableouterVar
hat. Gut! - Innerhalb des
outerFunc()
sehe ich, dass Sie eine FunktioninnerFunc()
definieren. - Innerhalb des
innerFunc()
kann ich eine VariableouterVar
ohne Deklaration sehen. Da ich lexical scoping verwende, betrachte ich die VariableouterVar
innerhalb voninnerFunc()
als die gleiche Variable wieouterVar
vonouterFunc()
.
Die destillierte Idee des lexikalischen Bereichs:
Der lexikalische Bereich besteht aus statisch festgelegten äußeren Bereichen.
Zum Beispiel:
const myGlobal = 0;function func() { const myVar = 1; console.log(myGlobal); // logs "0" function innerOfFunc() { const myInnerVar = 2; console.log(myVar, myGlobal); // logs "1 0" function innerOfInnerOfFunc() { console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0" } innerOfInnerOfFunc(); } innerOfFunc();}func();
Der lexikalische Scope von innerOfInnerOfFunc()
setzt sich aus den Scopes von innerOfFunc()
func()
und dem globalen Scope (dem äußersten Scope) zusammen. Innerhalb von innerOfInnerOfFunc()
können Sie auf die Variablen des lexikalischen Bereichs myInnerVar
myVar
und myGlobal
zugreifen.
Der lexikalische Bereich von innerFunc()
besteht aus func()
und dem globalen Bereich. Innerhalb von innerOfFunc()
können Sie auf die Variablen des lexikalischen Bereichs myVar
und myGlobal
zugreifen.
Schließlich besteht der lexikalische Bereich von func()
nur aus dem globalen Bereich. Innerhalb von func()
können Sie auf die lexikalische Scope-Variable myGlobal
zugreifen.
4. Die Closure
Mit dem lexikalischen Scope kann man statisch auf die Variablen des äußeren Scopes zugreifen. Es ist nur noch ein Schritt bis zur Closure!
Schauen wir uns noch einmal das outerFunc()
und innerFunc()
Beispiel an:
function outerFunc() { let outerVar = 'I am outside!'; function innerFunc() { console.log(outerVar); // => logs "I am outside!" } innerFunc();}outerFunc();
Innerhalb des innerFunc()
-Bereichs wird auf die Variable outerVar
aus dem lexikalischen Bereich zugegriffen. Das ist bereits bekannt.
Beachten Sie, dass der Aufruf von innerFunc()
innerhalb seines lexikalischen Bereichs (dem Bereich von outerFunc()
) erfolgt.
Lassen Sie uns eine Änderung vornehmen: innerFunc()
soll außerhalb seines lexikalischen Bereichs (außerhalb von outerFunc()
) aufgerufen werden. Würde innerFunc()
trotzdem auf outerVar
zugreifen können?
Lassen Sie uns die Anpassungen am Codeschnipsel vornehmen:
function outerFunc() { let outerVar = 'I am outside!'; function innerFunc() { console.log(outerVar); // => logs "I am outside!" } return innerFunc;}const myInnerFunc = outerFunc();myInnerFunc();
Jetzt wird innerFunc()
außerhalb seines lexikalischen Bereichs ausgeführt. Und was wichtig ist:
innerFunc()
hat immer noch Zugriff auf outerVar
aus seinem lexikalischen Bereich, auch wenn es außerhalb seines lexikalischen Bereichs ausgeführt wird.
Mit anderen Worten, innerFunc()
schließt die Variable outerVar
aus seinem lexikalischen Bereich ab (d.h. es fängt sie ein, merkt sie sich).
Mit anderen Worten, innerFunc()
ist eine Schließung, weil es die Variable outerVar
aus ihrem lexikalischen Bereich abschließt.
Sie haben den letzten Schritt gemacht, um zu verstehen, was eine Schließung ist:
Die Schließung ist eine Funktion, die auf ihren lexikalischen Bereich zugreift, auch wenn sie außerhalb ihres lexikalischen Bereichs ausgeführt wird.
Vereinfacht ausgedrückt, ist die Closure eine Funktion, die sich die Variablen an der Stelle merkt, an der sie definiert wird, unabhängig davon, wo sie später ausgeführt wird.
Eine Faustregel zur Identifizierung einer Closure: Wenn Sie in einer Funktion eine fremde Variable sehen (die nicht innerhalb der Funktion definiert ist), ist diese Funktion höchstwahrscheinlich eine Closure, weil die fremde Variable aufgefangen wird.
Im vorigen Codeschnipsel ist outerVar
eine Fremdvariable innerhalb der Closure innerFunc()
, die vom outerFunc()
-Bereich eingefangen wurde.
Lassen Sie uns mit Beispielen fortfahren, die zeigen, warum die Closure nützlich ist.
5. Closure-Beispiele
5.1 Event-Handler
Lassen Sie uns anzeigen, wie oft eine Schaltfläche angeklickt wird:
let countClicked = 0;myButton.addEventListener('click', function handleClick() { countClicked++; myText.innerText = `You clicked ${countClicked} times`;});
Öffnen Sie die Demo und klicken Sie auf die Schaltfläche. Der Text wird aktualisiert und zeigt die Anzahl der Klicks an.
Wenn die Schaltfläche angeklickt wird, wird handleClick()
irgendwo innerhalb des DOM-Codes ausgeführt. Die Ausführung erfolgt weit weg vom Ort der Definition.
Da es sich aber um eine Closure handelt, fängt handleClick()
das countClicked
aus dem lexikalischen Bereich ab und aktualisiert es, wenn ein Klick erfolgt. Mehr noch, auch myText
wird erfasst.
5.2 Callbacks
Das Erfassen von Variablen aus dem lexikalischen Bereich ist in Callbacks nützlich.
Ein setTimeout()
Callback:
const message = 'Hello, World!';setTimeout(function callback() { console.log(message); // logs "Hello, World!"}, 1000);
Das callback()
ist eine Closure, weil es die Variable message
erfasst.
Eine Iteratorfunktion für forEach()
:
let countEven = 0;const items = ;items.forEach(function iterator(number) { if (number % 2 === 0) { countEven++; }});countEven; // => 2
Das iterator
ist eine Closure, weil es die Variable countEven
erfasst.
5.3 Funktionale Programmierung
Currying passiert, wenn eine Funktion eine andere Funktion zurückgibt, bis die Argumente vollständig geliefert wurden.
Zum Beispiel:
function multiply(a) { return function executeMultiply(b) { return a * b; }}const double = multiply(2);double(3); // => 6double(5); // => 10const triple = multiply(3);triple(4); // => 12
multiply
ist eine Curry-Funktion, die eine andere Funktion zurückgibt.
Currying, ein wichtiges Konzept der funktionalen Programmierung, ist auch dank Closures möglich.
executeMultiply(b)
ist eine Closure, die a
aus ihrem lexikalischen Bereich abfängt. Wenn die Closure aufgerufen wird, werden die erfasste Variable a
und der Parameter b
zur Berechnung von a * b
verwendet.
6. Fazit
Der Scope regelt die Zugänglichkeit von Variablen in JavaScript. Es kann einen Funktions- oder einen Block-Scope geben.
Der lexikalische Scope erlaubt es einem Funktions-Scope, statisch auf die Variablen aus den äußeren Scopes zuzugreifen.
Schließlich ist eine Closure eine Funktion, die Variablen aus ihrem lexikalischen Bereich aufnimmt. In einfachen Worten: Die Closure merkt sich die Variablen von dem Ort, an dem sie definiert ist, egal wo sie ausgeführt wird.
Closures erfassen Variablen innerhalb von Event-Handlern, Callbacks. Sie werden in der funktionalen Programmierung verwendet. Außerdem könnte man Sie bei einem Frontend-Jobinterview fragen, wie Closures funktionieren.
Jeder JavaScript-Entwickler muss wissen, wie Closures funktionieren. Setzen Sie sich damit auseinander ⌐■_■.
Wie wäre es mit einer Herausforderung? 7 Interview-Fragen zu JavaScript-Closures. Können Sie sie beantworten?