Wenn Sie ein Objekt, eine Variable oder eine Funktion verwenden, tun Sie dies absichtlich. Sie denken: "Hier brauche ich eine Variable" und fügen sie Ihrem Code hinzu. Verschlüsse sind jedoch etwas anderes. Während die meisten Programmierer anfangen, etwas über Schließungen zu lernen, verwenden diese Leute bereits unwissentlich Schließungen. Wahrscheinlich passiert dir dasselbe. Daher geht es beim Lernen über Schließungen weniger darum, eine neue Idee zu lernen, als darum, etwas zu erkennen, auf das Sie schon oft gestoßen sind. Kurz gesagt, Schließen ist, wenn eine Funktion auf Variablen zugreift, die außerhalb von ihr deklariert wurden. Zum Beispiel ist der Verschluss in diesem Code enthalten:
let users = ['Alice', 'Dan', 'Jessica'];
let query = 'A';
let user = users.filter(user => user.startsWith(query));
Beachten Sie, dass es sich
user => user.startsWith(query)um eine Funktion handelt. Sie benutzt eine Variable query. Und diese Variable wird außerhalb der Funktion deklariert. Dies ist eine Schließung.
Sie können das Lesen überspringen, wenn Sie möchten. Der Rest dieses Materials betrachtet Verschlüsse in einem anderen Licht. Anstatt darüber zu sprechen, was Verschlüsse sind, wird in diesem Teil des Artikels auf die Details der Erkennung von Verschlüssen eingegangen. Dies ähnelt der Arbeitsweise der ersten Programmierer in den 1960er Jahren.
Schritt 1: Funktionen können auf Variablen zugreifen, die außerhalb von ihnen deklariert wurden
Um Abschlüsse zu verstehen, müssen Sie mit Variablen und Funktionen vertraut sein. In diesem Beispiel deklarieren wir eine Variable
foodinnerhalb einer Funktion eat:
function eat() {
let food = 'cheese';
console.log(food + ' is good');
}
eat(); // 'cheese is good'
Was ist, wenn Sie den Wert einer Variablen später
foodaußerhalb der Funktion ändern möchten eat? Zu diesem Zweck können wir die Variable selbst aus der Funktion entfernen foodund auf eine höhere Ebene verschieben:
let food = 'cheese'; //
function eat() {
console.log(food + ' is good');
}
Auf diese Weise können Sie die Variable
foodbei Bedarf "von außen" ändern :
eat(); // 'cheese is good'
food = 'pizza';
eat(); // 'pizza is good'
food = 'sushi';
eat(); // 'sushi is good'
Mit anderen Worten, die Variable ist
foodnicht mehr eatlokal für die Funktion . eatTrotzdem hat die Funktion keine Probleme beim Arbeiten mit dieser Variablen. Funktionen können auf Variablen zugreifen, die außerhalb von ihnen deklariert sind. Halten Sie eine Weile inne und überprüfen Sie sich selbst. Stellen Sie sicher, dass Sie keine Probleme mit dieser Idee haben. Sobald sich dieser Gedanke fest in Ihrem Kopf festgesetzt hat, fahren Sie mit dem zweiten Schritt fort.
Schritt 2: Platzieren des Codes im Funktionsaufruf
Nehmen wir an, wir haben Code:
/* */
Es spielt keine Rolle, um welchen Code es sich handelt. Angenommen, wir müssen es zweimal ausführen.
Der erste Weg, dies zu tun, besteht darin, einfach eine Kopie des Codes zu erstellen:
/* */
/* */
Eine andere Möglichkeit besteht darin, den Code in eine Schleife zu setzen:
for (let i = 0; i < 2; i++) {
/* */
}
Und der dritte Weg, der für uns heute besonders interessant ist, besteht darin, diesen Code in eine Funktion zu setzen:
function doTheThing() {
/* */
}
doTheThing();
doTheThing();
Die Verwendung einer Funktion bietet uns maximale Flexibilität, da wir den angegebenen Code beliebig oft, zu jeder Zeit und von jedem Ort im Programm aus aufrufen können.
Tatsächlich können wir uns bei Bedarf auf einen einzigen Aufruf der neuen Funktion beschränken:
function doTheThing() {
/* */
}
doTheThing();
Bitte beachten Sie, dass der obige Code dem Original-Code-Snippet entspricht:
/* */
Mit anderen Worten, wenn wir einen Code nehmen und ihn in eine Funktion "einwickeln" und diese Funktion dann genau einmal aufrufen, haben wir keinen Einfluss darauf, was genau dieser Code tut. Es gibt einige Ausnahmen von dieser Regel, auf die wir nicht achten werden, aber im Allgemeinen können wir davon ausgehen, dass diese Regel wahr ist. Denken Sie eine Weile darüber nach, gewöhnen Sie sich an diese Idee.
Schritt 3: Verschlüsse erkennen
Wir haben zwei Ideen herausgefunden:
- Funktionen können mit Variablen arbeiten, die außerhalb von ihnen deklariert sind.
- Wenn Sie den Code in eine Funktion einfügen und diese Funktion einmal aufrufen, hat dies keine Auswirkungen auf die Ergebnisse des Codes.
Lassen Sie uns nun darüber sprechen, was passieren wird, wenn diese beiden Ideen kombiniert werden.
Nehmen wir den Beispielcode, den wir uns im ersten Schritt angesehen haben:
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
Lassen Sie uns nun dieses ganze Beispiel in eine Funktion einfügen, die wir nur einmal aufrufen möchten:
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
}
liveADay();
Lesen Sie die beiden vorherigen Codebeispiele und stellen Sie sicher, dass sie gleichwertig sind.
Das zweite Beispiel funktioniert! Aber schauen wir es uns genauer an. Beachten Sie, dass sich die Funktion
eatinnerhalb einer Funktion befindet liveADay. Ist das in JavaScript erlaubt? Ist es wirklich möglich, eine Funktion in eine andere zu packen?
Es gibt Sprachen, in denen sich auf diese Weise strukturierter Code als falsch herausstellt. In C wäre ein solcher Code beispielsweise falsch (in dieser Sprache gibt es keine Verschlüsse). Dies bedeutet, dass bei Verwendung von C unsere zweite Schlussfolgerung falsch ist - Sie können nicht einfach einen beliebigen Code nehmen und ihn in eine Funktion "einschließen". In JavaScript gibt es jedoch keine solche Einschränkung.
Lassen Sie uns noch einmal über diesen Code nachdenken und dabei besonders darauf achten, wo die Variable deklariert ist und wo sie verwendet wird.
food::
function liveADay() {
let food = 'cheese'; // `food`
function eat() {
console.log(food + ' is good'); // `food`
}
eat();
}
liveADay();
Lassen Sie uns diesen Code Schritt für Schritt gemeinsam durchgehen. Zunächst deklarieren wir auf oberster Ebene eine Funktion
liveADay. Wir rufen sie sofort an. Diese Funktion hat eine lokale Variable food. Die Funktion ist auch darin deklariert eat. Dann wird die liveADayFunktion intern aufgerufen eat. Da sich die Funktion eatinnerhalb einer Funktion befindet liveADay, eat"sieht" sie alle in deklarierten Variablen liveADay. Aus diesem Grund kann eine Funktion eatden Wert einer Variablen lesen food.
Dies nennt man Schließung.
Wir sprechen über die Existenz eines Abschlusses, wenn eine Funktion (wie
eat) den Wert einer Variablen (wie) liest oder schreibt food, die außerhalb davon deklariert ist (zum Beispiel in einer Funktion liveADay).
Denken Sie über diese Wörter nach und lesen Sie sie erneut. Testen Sie sich selbst, indem Sie herausfinden, worüber wir in unserem Beispielcode sprechen.
Hier ist ein Beispiel, das ganz am Anfang des Artikels gegeben wurde:
let users = ['Alice', 'Dan', 'Jessica'];
let query = 'A';
let user = users.filter(user => user.startsWith(query));
Es ist möglicherweise einfacher, den Abschluss zu bemerken, wenn Sie dieses Beispiel mit einem Funktionsausdruck neu schreiben:
let users = ['Alice', 'Dan', 'Jessica'];
// 1. query
let query = 'A';
let user = users.filter(function(user) {
// 2.
// 3. query ( !)
return user.startsWith(query);
});
Wenn eine Funktion auf eine außerhalb deklarierte Variable zugreift, nennen wir sie einen Abschluss. Der Begriff selbst wird locker genug verwendet. Einige Benutzer rufen die verschachtelte Funktion selbst auf, die im Beispiel "Schließen" gezeigt wird. Andere verweisen möglicherweise auf einen externen Variablen-Accessor, indem sie ihn als "Abschluss" bezeichnen. In der Praxis spielt dies keine Rolle.
Funktionsaufruf Geist
Verschlüsse mögen Ihnen jetzt wie ein täuschend einfaches Konzept erscheinen. Dies bedeutet jedoch nicht, dass ihnen einige nicht offensichtliche Merkmale fehlen. Wenn Sie sorgfältig darüber nachdenken, dass eine Funktion die Werte von Variablen außerhalb lesen und schreiben kann, stellt sich heraus, dass dies ziemlich schwerwiegende Konsequenzen hat.
Dies bedeutet beispielsweise, dass solche Variablen "leben", solange eine in einer anderen Funktion verschachtelte Funktion aufgerufen werden kann.
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
// eat
setTimeout(eat, 5000);
}
liveADay();
In diesem Beispiel handelt es sich
foodum eine lokale Variable innerhalb eines Funktionsaufrufs liveADay(). Ich möchte nur entscheiden, dass diese Variable nach dem Beenden der Funktion "verschwindet" und nicht zurückkehrt, um uns wie ein Geist zu verfolgen.
In der Funktion
liveADaybitten wir den Browser, die Funktion eatnach fünf Sekunden aufzurufen . Und diese Funktion liest den Wert der Variablen food. Infolgedessen stellt sich heraus, dass die JavaScript-Engine die foodmit diesem Aufruf verknüpfte Variable liveADay()bis zum Aufruf der Funktion am Leben erhalten muss eat.
In diesem Sinne können Schließungen als "Geister" vergangener Funktionsaufrufe oder als "Erinnerungen" solcher Aufrufe angesehen werden. Obwohl die Ausführung der Funktion
liveADay()Vor langer Zeit beendet, müssen darin deklarierte Variablen so lange bestehen eatbleiben, wie die verschachtelte Funktion aufgerufen werden kann. Glücklicherweise kümmert sich JavaScript um diese Mechanismen, sodass wir in solchen Situationen nichts Besonderes tun müssen.
Warum werden "Verschlüsse" so genannt?
Sie fragen sich vielleicht, warum Verschlüsse so genannt werden. Der Grund dafür ist hauptsächlich historisch. Jeder, der mit Computerjargon vertraut ist, könnte sagen, dass ein Ausdruck wie dieser
user => user.startsWith(query)eine "offene Bindung" hat. Mit anderen Worten, aus diesem Ausdruck wird klar, was user(Parameter) ist, aber wenn man es isoliert betrachtet, ist es nicht klar, was es ist query. Wenn wir sagen, dass es sich tatsächlich um queryeine Variable handelt, die außerhalb der Funktion deklariert wurde, "schließen" wir diese offene Bindung. Mit anderen Worten, wir bekommen einen Abschluss.
Abschlüsse sind nicht in allen Programmiersprachen implementiert. Beispielsweise können in einigen Sprachen wie C verschachtelte Funktionen überhaupt nicht verwendet werden. Infolgedessen kann die Funktion nur mit ihren lokalen Variablen oder mit globalen Variablen arbeiten. Es gibt jedoch nie eine Situation, in der auf die lokalen Variablen der übergeordneten Funktion zugegriffen werden kann. Dies ist eigentlich eine sehr unangenehme Einschränkung.
Es gibt auch Sprachen wie Rust, die Verschlüsse implementieren. Sie verwenden jedoch eine andere Syntax, um Verschlüsse und normale Funktionen zu beschreiben. Wenn Sie den Wert einer Variablen außerhalb einer Funktion lesen müssen, müssen Sie daher mit Rust ein spezielles Konstrukt verwenden. Der Grund dafür ist, dass für die Verwendung von Abschlüssen möglicherweise die internen Mechanismen der Sprache erforderlich sind, um externe Variablen (als "Umgebung" bezeichnet) auch nach Abschluss des Funktionsaufrufs beizubehalten. Diese zusätzliche Belastung des Systems ist in JavaScript akzeptabel, kann jedoch bei Verwendung in relativ einfachen Sprachen zu Leistungsproblemen führen.
Nun hoffe ich, dass Sie das Konzept der Schließungen in JavaScript verstehen.
Haben Sie Schwierigkeiten, JavaScript-Konzepte zu verstehen?
