Detaillierte Erklärung des js-Abschlusses

Schließung

Klassische echte Fragen

  • Was ist eine Schließung? Welche Einsatzszenarien gibt es für Verschlüsse? Wie kann man den Verschluss zerstören?

Was ist Schließung?

Der Abschluss ist ein sehr wichtiger Wissenspunkt in JavaScript und einer der Wissenspunkte, die in unseren Front-End-Interviews am häufigsten gefragt werden.

Öffnen Sie „ JavaScript Advanced Programming“ und „ JavaScript Definitive Guide“ und Sie werden feststellen, dass es unterschiedliche Erklärungen zu Schließungen gibt. Wenn Sie im Internet nach Inhalten zu Schließungen suchen, werden Sie auch feststellen, dass es unterschiedliche Meinungen gibt, was dieses Wissen zu einem Punkt macht scheinen... Ein bisschen mysteriös, sogar ein bisschen fantasievoll.

Ist dieser Wissenspunkt also wirklich so tiefgreifend?

NEIN! Tatsächlich ist es sehr einfach, Schließungen in JavaScript zu verstehen. Zuvor müssen Sie jedoch die folgenden zwei Wissenspunkte kennen:

  • Geltungsbereich und Geltungsbereichskette in JavaScript
  • Garbage Collection in JavaScript

Hier gehen wir kurz auf diese beiden Wissenspunkte ein:

1. Bereich und Bereichskette in JavaScript

  • Der Bereich ist ein unabhängiges Gebiet, sodass Variablen nicht verloren gehen oder offengelegt werden und Variablen mit demselben Namen in verschiedenen Bereichen nicht in Konflikt geraten.
  • Der Geltungsbereich wird bei der Definition festgelegt und ändert sich nicht.
  • Wenn der Wert im aktuellen Bereich nicht gefunden wird, wird er im oberen Bereich durchsucht, bis der globale Bereich gefunden wird. Die durch einen solchen Suchvorgang gebildete Kette wird als Bereichskette bezeichnet.

2. Garbage Collection in JavaScript

  • Die Javascript- Ausführungsumgebung ist für die Verwaltung des während der Codeausführung verwendeten Speichers verantwortlich, wozu ein Garbage-Collection-Mechanismus gehört.
  • Der Garbage Collector findet regelmäßig (periodisch) die Variablen, die nicht mehr verwendet werden. Solange die Variable nicht mehr verwendet wird, wird sie vom Garbage Collector recycelt und gibt dann ihren Speicher frei. Wenn die Variable noch verwendet wird, wird sie nicht recycelt.

OK , mit diesen beiden Wissenspunkten wollen wir uns nun ansehen, was ein Abschluss ist.

Beim Schließen handelt es sich nicht um eine spezifische Technologie, sondern um ein Phänomen. Das bedeutet, dass beim Definieren einer Funktion Informationen in der Umgebung in der Funktion verwendet werden können. Mit anderen Worten: Wenn eine Funktion ausgeführt wird und Daten außerhalb der Funktion verwendet werden, wird ein Abschluss erstellt.

Die Scope-Kette ist genau das Mittel, um den Abschluss zu implementieren.

Was? Abschlüsse werden immer dann erstellt, wenn externe Daten in einer Funktion verwendet werden?

Wirklich? Nachfolgend können wir es beweisen:

Bild-20211227145016552

Im obigen Code definieren wir eine Variable i in der Funktion a und drucken diese i- Variable dann aus. Für die Funktion a existiert die Variable i in ihrem eigenen Funktionsbereich , sodass wir beim Debuggen sehen können, dass die Variable i in Local vorhanden ist .

Lassen Sie uns den obigen Code leicht ändern, wie unten gezeigt:

Bild-20211227145521272

Im obigen Code platzieren wir die Aktion der Deklaration der i- Variablen außerhalb der a- Funktion. Dies bedeutet, dass die a- Funktion die i- Variable nicht mehr in ihrem eigenen Bereich finden kann . Was wird sie tun?

Wenn Sie die Bereichskette untersucht haben, müssen Sie wissen, dass sie Schicht für Schicht entlang der Bereichskette angezeigt wird. Wie oben bei der Einführung von Abschlüssen erwähnt, wird in diesem Fall jedoch ein Abschluss erstellt, wenn die Funktion externe Daten verwendet.

Beobachten Sie den Debugging-Bereich sorgfältig. Wir werden feststellen, dass ich mich zu diesem Zeitpunkt in der Schließung befindet , was unsere vorherige Aussage bestätigt.

Sie sehen also, Verschlüsse sind eigentlich gar nicht so schwer zu verstehen. Wenn Sie das Gefühl haben, dass Ihnen ein Wort besonders schwerfällt, können Sie auch die Wortaufteilungsmethode verwenden. Dies ist ebenfalls eine bewährte Methode, die ich empfehle.

„Geschlossen“ kann als „geschlossener, geschlossener Kreislauf“ verstanden werden, und „Paket“ kann als „ein paketähnlicher Raum“ verstanden werden. Daher kann der Verschluss tatsächlich als geschlossener Raum betrachtet werden. Wofür wird dieser Raum also verwendet? ? Tatsächlich wird es zum Speichern von Variablen verwendet.

Bild-20211227163947135

Werden also alle Variablendeklarationen unter einer Funktion in den geschlossenen Raum des Abschlusses gestellt?

Nicht wirklich, ob es in den Abschluss eingefügt werden soll, hängt davon ab, ob an anderer Stelle ein Verweis auf diese Variable vorhanden ist, zum Beispiel:

Bild-20211227164333723

Im obigen Code werden in der Funktion c keine Variablen erstellt, aber i, j, k und x werden gedruckt . Diese Variablen existieren in den Funktionen a, b bzw. im globalen Bereich. Daher werden drei Abschlüsse erstellt und der globale Der Wert von i wird im Verschluss gespeichert , die Werte der Variablen j und k werden im Verschluss a gespeichert und der Wert der Variablen x wird im Verschluss b gespeichert .

Wenn Sie jedoch genau hinschauen, werden Sie feststellen, dass die y- Variable in der Funktion b nicht im Abschluss platziert ist. Ob sie also in den Abschluss eingefügt werden soll, hängt davon ab, ob auf die Variable verwiesen wird.

Natürlich haben Sie zu diesem Zeitpunkt möglicherweise ein so neues Problem, so viele Schließungen. Würde das nicht Speicherplatz beanspruchen?

Wenn es sich um einen automatisch gebildeten Verschluss handelt, wird er tatsächlich zerstört. Zum Beispiel:

Bild-20211227174043786

Im obigen Code versuchen wir, die Variable k in Zeile 16 auszudrucken . Offensichtlich wird zu diesem Zeitpunkt ein Fehler gemeldet. Durch Festlegen eines Haltepunkts in Zeile 16 zum Debuggen können wir deutlich erkennen, dass zu diesem Zeitpunkt kein Abschluss vorliegt . Der Garbage Collector fordert automatisch nicht referenzierte Variablen ohne Speichernutzung zurück.

Ich beziehe mich hier natürlich auf die automatische Generierung von Abschlüssen. Bei Abschlüssen müssen wir manchmal manuell einen Abschluss basierend auf den Anforderungen erstellen.

Betrachten Sie das folgende Beispiel:

function eat(){
    
    
    var food = "鸡翅";
    console.log(food);
}
eat(); // 鸡翅
console.log(food); // 报错

Im obigen Beispiel haben wir eine Funktion namens eat deklariert und aufgerufen.

Die JavaScript- Engine erstellt einen Ausführungskontext der Funktion eat , in dem die Variable food deklariert und zugewiesen wird.

Wenn die Methode ausgeführt wird, wird der Kontext zerstört und die Lebensmittelvariable verschwindet ebenfalls. Dies liegt daran, dass die Variable food eine lokale Variable der Funktion eat ist . Sie fungiert in der Funktion eat und wird erstellt und zerstört, wenn der Ausführungskontext von eat erstellt wird. Wenn wir also die Lebensmittelvariable erneut drucken, wird ein Fehler gemeldet, der uns mitteilt, dass die Variable nicht existiert.

Aber lassen Sie uns diesen Code leicht modifizieren:

function eat(){
    
    
    var food = '鸡翅';
    return function(){
    
    
        console.log(food);
    }
}
var look = eat();
look(); // 鸡翅
look(); // 鸡翅

In diesem Beispiel gibt die Funktion eat eine Funktion zurück, und in dieser inneren Funktion wird auf die lokale Variable food zugegriffen . Rufen Sie die Eat- Funktion auf und weisen Sie das Ergebnis der Look- Variablen zu. Dieser Look zeigt auf die interne Funktion in der Eat- Funktion, ruft sie dann auf und gibt schließlich den Wert von food aus.

Der Grund, warum auf Lebensmittel zugegriffen werden kann, ist sehr einfach: Wie oben erwähnt, recycelt der Garbage Collector nur Variablen, auf die nicht verwiesen wird, aber sobald eine Variable noch referenziert ist, wird der Garbage Collector diese Variable nicht recyceln. Im obigen Beispiel sollten Lebensmittel nach dem Aufruf von eat zerstört werden , aber wir haben die anonyme Funktion innerhalb von eat nach außen zurückgegeben , und diese anonyme Funktion verweist auf food , sodass der Garbage Collector sie nicht recycelt. Ja, aus diesem Grund ist der Wert von Die Lebensmittelvariable kann weiterhin ausgedruckt werden, wenn diese anonyme Funktion außerhalb aufgerufen wird .

An dieser Stelle kommt einer der Vorteile bzw. Eigenschaften von Verschlüssen zum Ausdruck, nämlich:

  • Durch den Abschluss kann die externe Umgebung auf die lokalen Variablen innerhalb der Funktion zugreifen.
  • Durch Abschlüsse können lokale Variablen beibehalten und nicht zusammen mit ihrem Kontext zerstört werden.

Mit dieser Funktion können wir ein globales Problem der variablen Verschmutzung lösen. In den frühen Tagen , als JavaScript nicht modularisiert werden konnte und mehrere Personen zusammenarbeiteten, konnte die Definition zu vieler globaler Variablen zu Konflikten bei der Benennung globaler Variablen führen. Verwenden Sie Abschlüsse, um Funktionsaufrufe an Variablen zu lösen und Variablen in einen unabhängigen Raum zu schreiben. Innerhalb, das das lösen kann Problem der globalen variablen Umweltverschmutzung bis zu einem gewissen Grad.

Zum Beispiel:

var name = "GlobalName";
// 全局变量
var init = (function () {
    
    
    var name = "initName";
    function callName() {
    
    
        console.log(name);
        // 打印 name
    }
    return function () {
    
    
        callName();
        // 形成接口
    }
}());
init(); // initName
var initSuper = (function () {
    
    
    var name = "initSuperName";
    function callName() {
    
    
        console.log(name);
        // 打印 name
    }
    return function () {
    
    
        callName();
        // 形成接口
    }
}());
initSuper(); // initSuperName

Okay, am Ende dieses Abschnitts machen wir eine kleine Zusammenfassung der Abschlüsse:

  • Ein Abschluss ist ein geschlossener Raum, der Werte des Bereichs speichert, auf die an anderer Stelle verwiesen wird. In JavaScript werden Abschlüsse durch Bereichsketten implementiert.

  • Solange externe Daten in der Funktion verwendet werden, wird ein Abschluss erstellt. In diesem Fall müssen wir uns nicht um den Abschluss kümmern, der beim Codieren erstellt wird.

  • Wir können auf irgendeine Weise auch manuell Abschlüsse erstellen, sodass die externe Umgebung auf die lokalen Variablen innerhalb der Funktion zugreifen kann, sodass die lokalen Variablen kontinuierlich gespeichert und nicht zusammen mit ihrem Kontext zerstört werden können.

Klassisches Schließungsproblem

Nachdem wir über Schließungen gesprochen haben, schauen wir uns ein klassisches Schließungsproblem an.

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

Im obigen Code besteht unser erwartetes Ergebnis darin, die Werte der i- Variablen nach 1 Sekunde als 1, 2 und 3 auszugeben . Das Ausführungsergebnis lautet jedoch: 4, 4, 4 .

Tatsächlich liegt das Problem bei Schließungen. Sie sehen, dass setTimeout in der Schleife auf seine äußere Variable i zugreift und so einen Abschluss bildet.

Da es nur eine i- Variable gibt , wird in setTimeout , das dreimal eine Schleife durchläuft , auf dieselbe Variable zugegriffen . Wenn die Schleife das vierte Mal erreicht , erhöht sich die i -Variable auf 4 , die Schleifenbedingung ist nicht erfüllt, die Schleife endet und der Kontext endet, nachdem der Code ausgeführt wurde. Allerdings warten die drei setTimeouts vor der Ausführung 1 Sekunde. Aufgrund der Schließung können sie zwar noch auf die Variable i zugreifen , der Wert der Variablen i beträgt zu diesem Zeitpunkt jedoch bereits 4 .

Um dieses Problem zu lösen, können wir die anonyme Funktion in setTimeout wie folgt dazu bringen, nicht mehr auf externe Variablen, sondern auf ihre eigenen internen Variablen zuzugreifen:

for (var i = 1; i <= 3; i++) {
    
    
    (function (index) {
    
    
        setTimeout(function () {
    
    
            console.log(index);
        }, 1000);
    })(i)
}

Auf diese Weise ist es nicht erforderlich, auf die Variable zuzugreifen, die ich in der for- Schleife in setTimeout deklariert habe . Stattdessen übergebe ich den Wert der Variablen i an setTimeout , indem ich eine Funktion zum Übergeben von Parametern aufrufe , sodass diese keinen Abschluss mehr erzeugen, da die Variable i in meinem eigenen Bereich zu finden ist .

Natürlich gibt es eine einfachere Möglichkeit, dieses Problem zu lösen, nämlich die Verwendung des Schlüsselworts let in ES6 .

Die von ihr deklarierte Variable hat einen Blockbereich. Wenn Sie sie in eine Schleife einfügen, gibt es bei jeder Schleife eine neue Variable i. Selbst wenn es also einen Abschluss gibt, ist dies kein Problem, da jeder Abschluss ein anderes i speichert Variable, dann ist das Problem gerade gelöst.

for (let i = 1; i <= 3; i++) {
    
    
    setTimeout(function () {
    
    
        console.log(i);
    }, 1000);
}

Antworten auf echte Fragen

  • Was ist eine Schließung? Welche Einsatzszenarien gibt es für Verschlüsse? Wie kann man den Verschluss zerstören?

Ein Abschluss ist ein geschlossener Raum, der Werte des Bereichs speichert, auf die an anderer Stelle verwiesen wird. In JavaScript werden Abschlüsse durch Bereichsketten implementiert.

Solange externe Daten in der Funktion verwendet werden, wird ein Abschluss erstellt. In diesem Fall müssen wir uns nicht um den Abschluss kümmern, der beim Codieren erstellt wird.

Wir können auf irgendeine Weise auch manuell Abschlüsse erstellen, sodass die externe Umgebung auf die lokalen Variablen innerhalb der Funktion zugreifen kann, sodass die lokalen Variablen kontinuierlich gespeichert und nicht zusammen mit ihrem Kontext zerstört werden können.

Der Einsatz von Verschlüssen kann ein globales Problem der variablen Verschmutzung lösen.

Wenn es sich um einen automatisch generierten Abschluss handelt, müssen wir uns keine Sorgen um die Zerstörung des Abschlusses machen. Wenn es sich um einen manuell erstellten Abschluss handelt, können wir die referenzierte Variable auf Null setzen, das heißt, die Variable manuell löschen, damit der nächste Wenn beim Recycling festgestellt wird, dass diese Variable keinen Verweis mehr hat, wird der auf Null gesetzte Betrag recycelt .


- EOF -

Ich denke du magst

Origin blog.csdn.net/qq_53461589/article/details/132740029
Empfohlen
Rangfolge