JavaScript-Ausführungskontext und Aufrufstapel

Wenn Sie ein JavaScript-Entwickler sind oder einer werden möchten, bedeutet dies, dass Sie die internen Mechanismen zum Ausführen von JS-Code verstehen müssen. Insbesondere ein Verständnis des Ausführungskontexts und des Aufrufstapels ist unbedingt erforderlich, um andere JavaScript-Konzepte zu beherrschen, z. B. das Erhöhen von Variablen, den Umfang und das Schließen. Das Material, dessen Übersetzung wir heute veröffentlichen, ist dem Ausführungskontext und dem Aufrufstapel in JavaScript gewidmet.



Ausführungskontext


Der Ausführungskontext ist vereinfacht ausgedrückt ein Konzept, das die Umgebung beschreibt, in der JavaScript-Code ausgeführt wird. Code wird immer in einem Kontext ausgeführt.

▍ Führen Sie Kontexttypen aus


JavaScript hat drei Arten von Ausführungskontexten:

  • Globaler Ausführungskontext. Dies ist der grundlegende Standardausführungskontext. Wenn sich ein Code nicht in einer Funktion befindet, gehört dieser Code zum globalen Kontext. Der globale Kontext ist durch das Vorhandensein eines globalen Objekts gekennzeichnet, das im Fall des Browsers das window ist, und durch die Tatsache, dass das this this auf dieses globale Objekt verweist. Ein Programm kann nur einen globalen Kontext haben.
  • Funktionsausführungskontext. Jedes Mal, wenn eine Funktion aufgerufen wird, wird ein neuer Kontext dafür erstellt. Jede Funktion hat ihren eigenen Ausführungskontext. Ein Programm kann gleichzeitig viele Kontexte zum Ausführen von Funktionen haben. Beim Erstellen eines neuen Kontexts für die Ausführung einer Funktion werden eine bestimmte Abfolge von Schritten durchlaufen, die im Folgenden erläutert werden.
  • Der Ausführungskontext der eval . Code, der innerhalb der eval Funktion ausgeführt wird, hat auch einen eigenen Ausführungskontext. Die eval wird jedoch sehr selten verwendet, daher werden wir hier nicht auf diesen Ausführungskontext eingehen.

Ausführungsstapel


Der Ausführungsstapel, der auch als Aufrufstapel bezeichnet wird, ist der LIFO-Stapel, der zum Speichern von Ausführungskontexten verwendet wird, die während der Codeausführung erstellt wurden.

Wenn die JS-Engine mit der Verarbeitung des Skripts beginnt, erstellt die Engine einen globalen Ausführungskontext und platziert ihn auf dem aktuellen Stapel. Wenn ein Befehl zum Aufrufen einer Funktion erkannt wird, erstellt die Engine einen neuen Ausführungskontext für diese Funktion und platziert ihn oben auf dem Stapel.

Die Engine führt eine Funktion aus, deren Ausführungskontext sich oben im Stapel befindet. Wenn die Funktion abgeschlossen ist, wird ihr Kontext aus dem Stapel entfernt und die Steuerung wird in den Kontext übertragen, der sich im vorherigen Element des Stapels befindet.

Wir werden diese Idee anhand des folgenden Beispiels untersuchen:

 let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context'); 

So ändert sich der Aufrufstapel, wenn dieser Code ausgeführt wird.


Call Stack Status

Wenn der obige Code in den Browser geladen wird, erstellt die JavaScript-Engine einen globalen Ausführungskontext und platziert ihn auf dem aktuellen Aufrufstapel. Wenn Sie die Funktion first() aufrufen, erstellt die Engine einen neuen Kontext für diese Funktion und platziert ihn oben auf dem Stapel.

Wenn die Funktion second() Funktion first() aufgerufen wird, wird für diese Funktion ein neuer Ausführungskontext erstellt und ebenfalls auf den Stapel verschoben. Nachdem die Funktion second() ihre Arbeit abgeschlossen hat, wird ihr Kontext vom Stapel entfernt und die Steuerung wird in den Ausführungskontext übertragen, der sich auf dem darunter liegenden Stapel befindet, dh in den Kontext der Funktion first() .

Wenn die Funktion first() wird, wird ihr Kontext aus dem Stapel entfernt und die Steuerung in den globalen Kontext übertragen. Nachdem der gesamte Code ausgeführt wurde, ruft die Engine den globalen Ausführungskontext vom aktuellen Stapel ab.

Informationen zum Erstellen von Kontexten und Ausführen von Code


Bisher haben wir darüber gesprochen, wie die JS-Engine Ausführungskontexte verwaltet. Lassen Sie uns nun darüber sprechen, wie Ausführungskontexte erstellt werden und was mit ihnen passiert, nachdem sie erstellt wurden. Insbesondere sprechen wir über die Phase der Erstellung des Ausführungskontexts und die Phase der Codeausführung.

▍ Phase der Erstellung des Ausführungskontexts


Bevor der JavaScript-Code ausgeführt wird, wird der Ausführungskontext erstellt. Bei der Erstellung werden drei Aktionen ausgeführt:

  1. Dieser Wert wird bestimmt und this (diese Bindung) ist gebunden.
  2. Die LexicalEnvironment Komponente wird erstellt.
  3. Die VariableEnvironment Komponente wird erstellt.

Konzeptionell kann der Ausführungskontext wie folgt dargestellt werden:

 ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, } 

Diese Bindung


Im globalen Ausführungskontext enthält this einen Verweis auf das globale Objekt (wie bereits erwähnt, ist es im Browser ein window ).

Im Kontext der Funktionsausführung hängt der Wert davon ab, wie die Funktion aufgerufen wurde. Wenn es als Methode eines Objekts aufgerufen wird, ist der Wert dieses Objekts an dieses Objekt gebunden. In anderen Fällen ist this an ein globales Objekt gebunden oder auf undefined (im strengen Modus). Betrachten Sie ein Beispiel:

 let foo = { baz: function() { console.log(this); } } foo.baz();    // 'this'    'foo',    'baz'              //    'foo' let bar = foo.baz; bar();       // 'this'     window,                 //      

Lexikalische Umgebung


Gemäß der ES6- Spezifikation ist Lexical Environment ein Begriff, der verwendet wird, um die Beziehung zwischen Bezeichnern und einzelnen Variablen und Funktionen basierend auf der Struktur der lexikalischen Verschachtelung des ECMAScript-Codes zu definieren. Die lexikalische Umgebung besteht aus einem Umgebungsdatensatz und einem Verweis auf die externe lexikalische Umgebung, die null .

Einfach ausgedrückt ist eine lexikalische Umgebung eine Struktur, in der Informationen über die Entsprechung von Bezeichnern und Variablen gespeichert werden. Mit "Bezeichner" ist hier der Name einer Variablen oder Funktion gemeint, und mit "Variable" ist eine Referenz auf ein bestimmtes Objekt (einschließlich einer Funktion) oder einen primitiven Wert gemeint.

In der lexikalischen Umgebung gibt es zwei Komponenten:

  1. Aufzeichnung einer Umgebung. Hier werden Variablen- und Funktionsdeklarationen gespeichert.
  2. Link zur externen Umgebung. Das Vorhandensein eines solchen Links zeigt an, dass die lexikalische Umgebung Zugriff auf die übergeordnete lexikalische Umgebung (Bereich) hat.

Es gibt zwei Arten von lexikalischen Umgebungen:

  1. Die globale Umgebung (oder der globale Ausführungskontext) ist eine lexikalische Umgebung ohne externe Umgebung. Der globale Umgebungsverweis auf die externe Umgebung ist null . In der globalen Umgebung (im Umgebungsdatensatz) sind integrierte Sprachentitäten (wie Object , Array usw.) verfügbar, die dem globalen Objekt zugeordnet sind. Außerdem sind vom Benutzer globale Variablen definiert. Der Wert in dieser Umgebung zeigt auf ein globales Objekt.
  2. Die Umgebung der Funktion, in der im Umgebungsdatensatz die vom Benutzer deklarierten Variablen gespeichert sind. Der Verweis auf die externe Umgebung kann sowohl ein globales Objekt als auch eine Funktion außerhalb der betreffenden Funktion angeben.

Es gibt zwei Arten von Umgebungsdatensätzen:

  1. Ein deklarativer Umgebungsdatensatz, in dem Variablen, Funktionen und Parameter gespeichert sind.
  2. Ein Umgebungsobjektdatensatz, in dem Informationen zu Variablen und Funktionen in einem globalen Kontext gespeichert werden.

Infolgedessen wird in einer globalen Umgebung ein Umgebungsdatensatz durch einen Objektumgebungsdatensatz und in einer Funktionsumgebung durch einen deklarativen Umgebungsdatensatz dargestellt.

Beachten Sie, dass in der Umgebung der Funktion der deklarative Datensatz der Umgebung auch das arguments enthält, in dem die Entsprechung zwischen den Indizes und den Werten der an die Funktion übergebenen Argumente sowie Informationen zur Anzahl solcher Argumente gespeichert sind.

Die lexikalische Umgebung kann als folgender Pseudocode dargestellt werden:

 GlobalExectionContext = { LexicalEnvironment: {   EnvironmentRecord: {     Type: "Object",     //        }   outer: <null> } } FunctionExectionContext = { LexicalEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //        }   outer: <        > } } 

Umgebungsvariablen


Eine variable Umgebung ist auch eine lexikalische Umgebung, in deren Umgebungsdatensatz die mit VariableStatement Befehlen erstellten Bindungen im aktuellen Ausführungskontext gespeichert sind.

Da die Umgebung von Variablen auch eine lexikalische Umgebung ist, besitzt sie alle oben beschriebenen Eigenschaften der lexikalischen Umgebung.

In ES6 gibt es einen Unterschied zwischen den Komponenten LexicalEnvironment und VariableEnvironment . Es besteht in der Tatsache, dass ersteres zum Speichern von Deklarationen von Funktionen und Variablen verwendet wird, die mit den Schlüsselwörtern let und const deklariert wurden, und letzteres nur zum Speichern von Variablenbindungen, die mit dem Schlüsselwort var deklariert wurden.

Betrachten Sie Beispiele, die veranschaulichen, was wir gerade besprochen haben:

 let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30); 

Eine schematische Darstellung des Ausführungskontexts für diesen Code sieht folgendermaßen aus:

 GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {   EnvironmentRecord: {     Type: "Object",     //          a: < uninitialized >,     b: < uninitialized >,     multiply: < func >   }   outer: <null> }, VariableEnvironment: {   EnvironmentRecord: {     Type: "Object",     //          c: undefined,   }   outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //          Arguments: {0: 20, 1: 30, length: 2},   },   outer: <GlobalLexicalEnvironment> }, VariableEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //          g: undefined   },   outer: <GlobalLexicalEnvironment> } } 

Wie Sie wahrscheinlich bemerkt haben, haben Variablen und Konstanten, die mit den Schlüsselwörtern let und const deklariert wurden, keine zugeordneten Werte, und Variablen, die mit dem Schlüsselwort var deklariert wurden, werden auf undefined .

Dies liegt daran, dass der Code während der Erstellung des Kontexts nach Deklarationen von Variablen und Funktionen sucht, während die Deklarationen von Funktionen vollständig in der Umgebung gespeichert werden. Die Werte von Variablen werden bei Verwendung von var auf undefined , und bei Verwendung von let oder const bleiben sie nicht initialisiert.

Aus diesem Grund können Sie auf Variablen zugreifen, die mit var deklariert wurden, bevor sie deklariert wurden (obwohl sie undefined ). Wenn Sie jedoch versuchen, auf Variablen oder Konstanten zuzugreifen, die mit let und const deklariert wurden, bevor sie deklariert wurden, tritt ein Fehler auf .

Was wir gerade beschrieben haben, heißt "Hubvariablen". Variablendeklarationen „steigen“ an die Spitze ihres lexikalischen Bereichs, bevor Operationen ausgeführt werden, bei denen ihnen Werte zugewiesen werden.

▍ Phase der Codeausführung


Dies ist vielleicht der einfachste Teil dieses Materials. In diesem Stadium werden die Werte den Variablen zugewiesen und der Code wird ausgeführt.

Beachten Sie, dass die JS-Engine, wenn sie während der Ausführung des Codes den Wert der mit dem Schlüsselwort let am Deklarationsort deklarierten Variablen nicht finden kann, dieser Variablen den undefined Wert undefined .

Zusammenfassung


Wir haben gerade die internen Mechanismen zur Ausführung von JavaScript-Code besprochen. Um ein sehr guter JS-Entwickler zu sein, ist es zwar nicht erforderlich, all dies zu wissen. Wenn Sie jedoch die oben genannten Konzepte verstehen, können Sie andere Mechanismen der Sprache besser und tiefer verstehen, z. B. das Erhöhen von Variablen, den Umfang usw. Kurzschlüsse.

Liebe Leser! Was halten Sie außer dem Ausführungskontext und dem Aufrufstapel für JavaScript-Entwickler für nützlich?

Source: https://habr.com/ru/post/de422089/


All Articles