Hallo habr
Ich habe seit einiger Zeit nichts mehr geschrieben, viel Arbeit am Projekt in den letzten Wochen, aber jetzt habe ich freie Zeit und habe mich entschlossen, Ihnen einen neuen Artikel vorzustellen.
Heute werden wir weiterhin wichtige EcmaScript-Konzepte analysieren, über die lexikalische Umgebung und Closure sprechen. Das Verständnis des Konzepts der lexikalischen Umgebung ist sehr wichtig, um das Schließen zu verstehen, und das Schließen ist die Grundlage für so viele gute Techniken und Technologien in der JS-Welt (die auf der EcmaScript-Spezifikation basiert).
Also fangen wir an.
Lexikalische Umgebung (Lexikalische Umgebung, LO, LE)
Die offizielle ES6-Spezifikation definiert diesen Begriff als:
Die lexikalische Umgebung ist eine Art von Spezifikation, die zum Auflösen von Bezeichnernamen bei der Suche nach bestimmten Variablen und Funktionen auf der Grundlage der lexikalischen Struktur der ECMAScript-Codeverschachtelung verwendet wird. Die lexikalische Umgebung besteht aus einer Aufzeichnung der Umgebung und möglicherweise einem Nullverweis auf die externe lexikalische Umgebung.
Schauen wir uns das genauer an.
Ich stelle mir die lexikalische Umgebung als eine Art Struktur vor, in der die Verknüpfung von Kontextkennungen mit ihrer Bedeutung gespeichert ist. Dies ist eine Art Repository von Variablen, Funktionen und Klassen, die im Rahmen dieses Kontexts deklariert wurden.
Technisch gesehen ist LO ein Objekt mit zwei Eigenschaften:
- Umgebungsdatensatz (hier werden alle Anzeigen gespeichert)
- Link zum generativen Kontext von LO.
Über eine Verknüpfung zum übergeordneten Kontext des aktuellen Kontexts können wir bei Bedarf eine Verknüpfung zum "übergeordneten Kontext" des übergeordneten Kontexts und so weiter zum globalen Kontext herstellen, dessen übergeordnete Verknüpfung null ist. Aus dieser Definition folgt, dass die lexikalische Umgebung die Verbindung der Entität mit den Kontexten ihres Ursprungs ist. Eine Art ScopeChain in Funktionen ist eine Entsprechung der lexikalischen Umgebung. Wir haben in
diesem Artikel ausführlich über ScopeChain gesprochen.
let x = 10; let y = 20; const foo = z => { let x = 100; return x + y + z; } foo(30);
Technisch erfolgt die Auflösung von Bezeichnernamen wie in ScopeChain, d. H. Die Objekte in der LO-Schleife werden nacheinander abgefragt, bis der gewünschte Bezeichner gefunden wurde. Wenn der Bezeichner nicht gefunden wird, dann ReferenceError.
Die lexikalische Umgebung wird in der Phase der Erstellung des Kontexts erstellt und gefüllt. Wenn der aktuelle Kontext ausgeführt wurde, wird er aus dem Aufrufstapel entfernt, aber seine lexikalische Umgebung kann so lange weiterleben, wie mindestens eine Verknüpfung zu ihr besteht. Dies ist einer der Vorteile eines modernen Ansatzes zur Gestaltung von Programmiersprachen. Ich denke, es lohnt sich darüber zu reden!
Stapelorganisation im Vergleich zu dynamisch gemeinsam genutztem Speicher
In Stapelsprachen werden lokale Variablen auf dem Stapel gespeichert. Diese werden beim Aktivieren der Funktion aktualisiert. Wenn die Funktion beendet wird, werden ihre lokalen Variablen vom Stapel entfernt.
Bei einer gestapelten Organisation ist es nicht möglich, eine lokale Funktion von einer Funktion zurückzugeben oder eine Funktion für eine freie Variable aufzurufen.
Eine freie Variable ist eine Variable, die von einer Funktion verwendet wird, sie ist jedoch weder ein formaler Parameter noch eine lokale Variable für diese Funktion.
function testFn() { var locaVar = 10;
Mit der Stack-Organisation wäre weder eine locaVar-Suche in der externen LexicalEnvironment noch die Rückgabe der innerFn-Funktion möglich, da innerFn ist auch eine lokale Deklaration für testFn. Nach Abschluss von testFn werden alle lokalen Variablen einfach aus dem Stapel entfernt.
Daher wurde ein anderes Konzept vorgeschlagen - das Konzept des dynamisch zugewiesenen Speichers (Heap, Heep) + Garbage Collector + Referenzzählung. Der Kern dieses Konzepts ist einfach: Solange mindestens ein Verweis auf ein Objekt vorhanden ist, wird es nicht aus dem Speicher gelöscht. Weitere Details finden Sie
hier .
Schließung (Verschlüsse)
Ein Abschluss ist eine Kombination aus einem Codeblock und Daten des Kontexts, in dem dieser Block generiert wird, d. H. Es ist die Beziehung der Entität zu den generierenden Kontexten durch eine Kette von LO oder SopeChain.
Lassen Sie mich einen sehr guten
Artikel zu diesem Thema zitieren:
function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter();
Wenn die Personenfunktion ausgeführt wird, erstellt JavaScript einen neuen Ausführungskontext und die lexikalische Umgebung für die Funktion. Nach Abschluss dieser Funktion wird die displayName-Funktion zurückgegeben und der Variablen peter zugewiesen.
Ihre lexikalische Umgebung sieht also so aus:
personLexicalEnvironment = { environmentRecord: { name : 'Peter', displayName: < displayName function reference> } outer: <globalLexicalEnvironment> }
Wenn die Personenfunktion abgeschlossen ist, wird ihr Ausführungskontext aus dem Stapel entfernt. Die lexikalische Umgebung bleibt jedoch im Gedächtnis, da sich die lexikalische Umgebung der internen displayName-Funktion darauf bezieht. Somit sind seine Variablen weiterhin im Speicher verfügbar.
Wenn die Peter-Funktion ausgeführt wird (die eigentlich auf die displayName-Funktion verweist), erstellt JavaScript einen neuen Ausführungskontext und eine neue lexikalische Umgebung für diese Funktion.
So sieht seine lexikalische Umgebung aus:
displayNameLexicalEnvironment = { environmentRecord: { } outer: <personLexicalEnvironment> }
In der displayName-Funktion gibt es keine Variable, ihr Umgebungsdatensatz ist leer. Während der Ausführung dieser Funktion versucht JavaScript, die Namensvariable in der lexikalischen Umgebung der Funktion zu finden.
Da die lexikalische Umgebung der Funktion displayName keine Variablen enthält, wird in der externen lexikalischen Umgebung gesucht, dh in der lexikalischen Umgebung der Personenfunktion, die sich noch im Speicher befindet. JavaScript findet diese Variable und der Name wird auf der Konsole ausgegeben.
Das wichtigste Merkmal eines Abschlusses in ES ist, dass ein statischer Bereich verwendet wird (in einigen anderen Sprachen, die den Abschluss verwenden, ist die Situation anders).
Ein Beispiel:
var a = 5; function testFn() { alert(a); } (function(funArg) { var a = 20; funArg();
Eine weitere wichtige Verschlusseigenschaft ist die folgende Situation:
var first; var second; function testFn() { var a = 10; first = function() { return ++a; } second = function() { return --a; } a = 2; first();
Das heißt wir sehen, dass die freie Variable, die in den Abschlüssen mehrerer Funktionen vorhanden ist, durch Bezugnahme von ihnen geändert wird.
Fazit
Im Rahmen dieses Artikels haben wir kurz zwei zentrale Konzepte für EcmaScript beschrieben: Lexical Environment und Closure. In der Tat sind diese beiden Themen viel weiter gefasst. Wenn die Community den Wunsch hat, eine detailliertere Beschreibung der Unterschiede zwischen verschiedenen Arten von lexikalischen Umgebungen zu erhalten oder zu erfahren, wie v8 einen Abschluss erstellt, schreiben Sie darüber in den Kommentaren.