Beginnen wir mit einer einfachen Frage:
<script type="text/javascript">
alarm(i); // ?
var i = 1;
</script>
Das Ausgabeergebnis ist undefiniert. Dieses Phänomen wird als „Voranalyse“ bezeichnet: Die JavaScript-Engine analysiert zuerst Variablenvariablen und Funktionsdefinitionen. Der Code wird erst ausgeführt, wenn die Voranalyse abgeschlossen ist. Wenn ein Dokumentstrom mehrere Skriptcodesegmente enthält (durch Skript-Tags getrennter JS-Code oder importierte JS-Dateien), lautet die Reihenfolge:
Schritt 1. Lesen Sie das erste Codesegment
Schritt 2. Wenn ein Fehler auftritt, wird ein Syntaxfehler gemeldet (z. B. nicht übereinstimmende Klammern usw.) und mit Schritt 5 fortgefahren.
Schritt 3. Führen Sie eine „Vorabanalyse“ der Variablen- und Funktionsdefinitionen durch (es werden nie Fehler gemeldet, da nur korrekte Deklarationen analysiert werden).
Schritt 4. Führen Sie das Codesegment aus und melden Sie einen Fehler, wenn ein Fehler vorliegt (z. B. wenn die Variable undefiniert ist).
Schritt 5. Wenn ein weiteres Codesegment vorhanden ist, lesen Sie das nächste Codesegment und wiederholen Sie Schritt 2.
Schritt 6: Am Ende der obigen Analyse konnten viele Probleme erklärt werden, aber ich habe immer das Gefühl, dass etwas fehlt. Was genau ist beispielsweise in Schritt 3 „Vorparsen“? Schauen Sie sich in Schritt 4 das folgende Beispiel an:
<script type="text/javascript">
Alert(i); // Fehler: i ist nicht definiert.
ich = 1;
</script>
Warum verursacht der erste Satz einen Fehler? Müssen Variablen in JavaScript nicht undefiniert sein?
Die Zeit des Kompilierungsprozesses verging wie ein weißes Pferd, und ich öffnete die „Grundsätze der Kompilierung“ neben dem Bücherregal, als wäre sie eine Welt entfernt. In der vertrauten, aber ungewohnten Leerstelle befand sich dieser Hinweis:
Für traditionell kompilierte Sprachen Die Kompilierungsschritte sind unterteilt in: lexikalische Analyse und Syntaxanalyse, semantische Prüfung, Codeoptimierung und Bytegenerierung.
Bei interpretierten Sprachen kann jedoch mit der Interpretation und Ausführung begonnen werden, nachdem der Syntaxbaum durch lexikalische Analyse und Syntaxanalyse ermittelt wurde.
Einfach ausgedrückt besteht die lexikalische Analyse darin, einen Zeichenstrom (Char-Strom) in einen Token-Strom (Token-Strom) umzuwandeln, z. B. c = a - b in:
NAME „c“
umzuwandeln.
GLEICH
NAME „a“
MINUS
NAME „b“
SEMIKOLON
Das Obige ist nur ein Beispiel. Weitere Informationen finden Sie in
Kapitel 2 von „The Definitive Guide to JavaScript“, in dem es um die lexikalische Struktur geht, die auch in ECMA-262 beschrieben wird. Die lexikalische Struktur ist die Grundlage einer Sprache und leicht zu beherrschen. Die Implementierung der lexikalischen Analyse ist ein anderes Forschungsgebiet und wird hier nicht untersucht.
Wir können die Analogie der natürlichen Sprache verwenden, um eine Eins-zu-Eins-Übersetzung durchzuführen. Wenn wir beispielsweise einen Absatz aus dem Englischen Wort für Wort ins Chinesische übersetzen, erhalten wir eine Reihe von Token-Streams, was schwierig ist zu verstehen. Die weitere Übersetzung erfordert eine grammatikalische Analyse. Die folgende Abbildung ist ein Syntaxbaum einer bedingten Anweisung:
Wenn beim Erstellen des Syntaxbaums festgestellt wird, dass er nicht erstellt werden kann, z. B. if(a { i = 2; }, wird ein Syntaxfehler gemeldet und die Analyse des gesamten Codeblocks wird beendet. Dies ist Schritt 2 unter Der Anfang dieses Artikels.
Nach dem Syntaxbaum kann der übersetzte Satz immer noch mehrdeutig sein und eine weitere semantische Prüfung ist erforderlich. Bei herkömmlichen, stark typisierten Sprachen ist der Hauptteil der semantischen Prüfung die Typprüfung Tatsächliche Parameter von Funktionen und ob die formalen Parametertypen übereinstimmen. Für schwach typisierte Sprachen ist dieser Schritt möglicherweise nicht verfügbar (ich habe nur begrenzte Energie und keine Zeit, mir die Implementierung der JS-Engine anzusehen, daher bin ich nicht sicher, ob es eine gibt). Schritt zur semantischen Prüfung in der JS-Engine
. Es stellt sich heraus, dass für JavaScript-Engines eine lexikalische Analyse und eine Syntaxanalyse erforderlich sind. Anschließend können Schritte wie die semantische Prüfung und die Codeoptimierung durchgeführt werden (jede Sprache hat dies getan). Ein Kompilierungsprozess, aber interpretierte Sprachen werden nicht in Binärcode kompiliert.
Der obige Kompilierungsprozess kann das „Vorparsen“ am Anfang des Artikels noch nicht erklären. Wir müssen die Ausführung sorgfältig untersuchen
DerJavaScript
-Code
wurde im zweiten Teil von „Programming Practice“ sehr sorgfältig analysiert
In einen Syntaxbaum übersetzt und dann sofort gemäß dem Syntaxbaum ausgeführt,
der eine weitere Ausführung erfordert. Verstehen Sie, dass der Gültigkeitsbereich von JavaScript den lexikalischen Bereich verwendet Das heißt, der lexikalische Bereich hängt vom Quellcode ab. Der Compiler kann ihn durch statische Analyse bestimmen. Es sollte jedoch beachtet werden, dass die Semantik von with und eval kann nicht nur durch statische Technologie realisiert werden. Tatsächlich können wir nur über den lexikalischen Gültigkeitsbereich sprechen.
Wenn die JS-Engine einen Ausführungskontext erstellt Das Aufrufobjekt ist eine scriptObject-Struktur, die zum Speichern der internen Variablentabelle wie varDecls, der eingebetteten Funktionstabelle funDecls und der übergeordneten Referenzliste verwendet wird (Hinweis: Informationen wie varDecls und funDecls werden während des Aufrufs abgerufen). Die Syntaxanalysephase wird im Syntaxbaum gespeichert. Wenn die Funktionsinstanz ausgeführt wird, werden diese Informationen vom Syntaxbaum in das Skriptobjekt kopiert, das sich auf die Funktion bezieht und mit dem Lebenszyklus der Funktionsinstanz übereinstimmt .
Der lexikalische Bereich ist der Bereichsmechanismus von JS, und Sie müssen auch seine Implementierungsmethode verstehen. Dies ist die Bereichskette. Die Bereichskette ist ein Namenssuchmechanismus. Sie sucht zunächst nach dem scriptObject in der aktuellen Ausführungsumgebung. Wenn es nicht gefunden wird, folgt sie dem Upvalue zum übergeordneten scriptObject und sucht nach dem globalen Objekt.
Wenn eine Funktionsinstanz ausgeführt wird, wird ein Abschluss erstellt oder mit ihr verknüpft. scriptObject wird verwendet, um funktionsbezogene Variablentabellen statisch zu speichern, während der Abschluss diese Variablentabellen und ihre laufenden Werte während der Ausführung dynamisch speichert. Der Lebenszyklus eines Abschlusses kann länger sein als der einer Funktionsinstanz. Die Funktionsinstanz wird automatisch zerstört, nachdem die aktive Referenz leer ist, und der Abschluss wird von der JS-Engine recycelt, nachdem die Datenreferenz leer ist (in einigen Fällen wird sie nicht automatisch recycelt, was zu einem Speicherverlust führt).
Lassen Sie sich von den oben genannten Substantiven nicht einschüchtern. Sobald Sie die Konzepte der Ausführungsumgebung, des Aufrufobjekts, des Abschlusses, des lexikalischen Bereichs und der Bereichskette verstanden haben, können viele Phänomene in der JS-Sprache leicht gelöst werden.
Zusammenfassung An dieser Stelle lassen sich die Fragen am Anfang des Artikels sehr anschaulich erklären:
Das sogenannte „Pre-Parsing“ in Schritt 3 wird tatsächlich in der Syntaxanalysephase von Schritt 2 abgeschlossen und im Syntaxbaum gespeichert. Wenn eine Funktionsinstanz ausgeführt wird, werden varDelcs und funcDecls aus dem Syntaxbaum in das scriptObject der Ausführungsumgebung kopiert.
In Schritt 4 bedeuten undefinierte Variablen, dass sie nicht in der Variablentabelle von scriptObject gefunden werden können. Wenn keines von beiden gefunden wird, entspricht die Schreiboperation i = 1; i = 1; fügt dem Fensterobjekt ein neues Attribut hinzu. Wenn bei Lesevorgängen das auf die globale Ausführungsumgebung zurückverfolgte Skriptobjekt nicht gefunden werden kann, tritt ein Laufzeitfehler auf.
Nachdem ich verstanden hatte, lichtete sich der Nebel, die Blumen blühten und der Himmel wurde klar.
Abschließend möchte ich Ihnen eine Frage stellen:
<script type="text/javascript">
var arg = 1;
Funktion foo(arg) {
alarm(arg);
var arg = 2;
}
foo(3);
</script>
Was ist die Ausgabe der Warnung?