JavaScript unter der Haube

Nov 28 2022
Inhaltsverzeichnis In diesem Artikel tauchen wir in das Innenleben von JavaScript ein und wie es tatsächlich läuft. Wenn Sie die Details verstehen, verstehen Sie das Verhalten Ihres Codes und können daher bessere Apps schreiben.

Inhaltsverzeichnis

  • Thread und Call-Stack
  • Ausführungskontext
  • Ereignisschleife und asynchrones JavaScript
  • Speicher und Garbage Collection
  • JIT-Kompilierung (Just In Time).
  • Zusammenfassung

In diesem Artikel tauchen wir in die innere Funktionsweise von JavaScript ein und wie es tatsächlich ausgeführt wird. Wenn Sie die Details verstehen, verstehen Sie das Verhalten Ihres Codes und können daher bessere Apps schreiben.

JavaScript wird beschrieben als:

Single-Threaded, Garbage Collection, interpretierte oder Just In Time kompilierte Programmiersprache mit einer nicht blockierenden Ereignisschleife.

Lassen Sie uns jeden dieser Schlüsselbegriffe entpacken.

Thread & Call-Stack:

Die JavaScript-Engine ist ein Singlethread-Interpreter, der aus einem Heap und einem einzelnen Aufrufstapel besteht, der zum Ausführen des Programms verwendet wird.
Der Aufrufstapel ist eine Datenstruktur, die das LIFO-Prinzip (Last In, First Out) verwendet, um den Funktionsaufruf (Aufruf) vorübergehend zu speichern und zu verwalten.
Das bedeutet, dass die letzte Funktion, die in den Stapel geschoben wird, die erste ist, die herausspringt, wenn die Funktion zurückkehrt.
Da es sich um einen einzigen Aufrufstapel handelt, erfolgt die Ausführung der Funktion(en) einzeln von oben nach unten. Dies bedeutet, dass der Aufrufstapel synchron ist.

Nun, da es synchron ist, werden Sie sich fragen, wie JavaScript mit asynchronen Aufrufen umgehen kann.
Nun, die Ereignisschleife ist das Geheimnis hinter der asynchronen Programmierung von JavaScript.
Aber bevor wir das Konzept von asynchronen Aufrufen in JavaScript erklären und wie es mit Singlethread-Sprache möglich ist, wollen wir zuerst verstehen, wie der Code ausgeführt wird.

Ausführungskontext (EC):

Ausführungskontext ist definiert als die Umgebung, in der der JavaScript-Code ausgeführt wird.
Die Erstellung eines Ausführungskontexts erfolgt in zwei Phasen:

1. Phase der Erinnerungserstellung:

  • Erstellen des globalen Objekts (das im Browser als Fensterobjekt und in NodeJS als globales Objekt bezeichnet wird).
  • Erstellen des „this“-Objekts und Binden an das globale Objekt.
  • Einrichten eines Speicherheaps (Ein Heap ist ein großer, meist unstrukturierter Speicherbereich) zum Speichern von Variablen und Funktionsreferenzen.
  • Speichern von Funktionen und Variablen im globalen Ausführungskontext durch Implementieren von Hoisting .

Nachdem wir nun die Schritte hinter der Codeausführung kennen, kehren wir zu den zurück

Ereignisschleife:

Beginnen wir zunächst mit einem Blick auf dieses Diagramm:

Ereignisschleife in JS

Wir haben die Engine, die aus zwei Hauptkomponenten besteht:
* Memory Heap – hier findet die Speicherzuweisung statt.
* Call Stack – hier befinden sich Ihre Stack-Frames, während Ihr Code ausgeführt wird.

Wir haben die Web-APIs, die Threads sind, auf die Sie nicht zugreifen können, Sie können sie einfach aufrufen. Sie sind die Teile des Browsers, in denen Parallelität eintritt, wie DOM, AJAX, setTimeout und vieles mehr.

Schließlich gibt es noch die Callback-Warteschlange, die eine Liste von zu verarbeitenden Ereignissen ist. Jedes Ereignis hat eine zugeordnete Funktion, die aufgerufen wird, um es zu verarbeiten.

Welche Aufgabe hat hier also die Ereignisschleife?
Die Ereignisschleife hat eine einfache Aufgabe – den Call Stack und die Callback Queue zu überwachen. Wenn der Aufrufstapel leer ist, nimmt die Ereignisschleife das erste Ereignis aus der Warteschlange und schiebt es an den Aufrufstapel, der es effektiv ausführt.
Eine solche Iteration wird in der Ereignisschleife als Tick bezeichnet. Jedes Ereignis ist nur ein Funktionsrückruf.

Speicher und Garbage Collection:

Um die Notwendigkeit der Garbage Collection zu verstehen, müssen wir zuerst den Speicherlebenszyklus verstehen, der für jede Programmiersprache ziemlich gleich ist, er besteht aus 3 Hauptschritten.
1. Ordnen Sie den Speicher zu.
2. Verwenden Sie den zugewiesenen Speicher entweder zum Lesen oder Schreiben oder für beides.
3. Geben Sie den zugewiesenen Speicher frei, wenn er nicht mehr benötigt wird.

Die meisten Speicherverwaltungsprobleme treten auf, wenn wir versuchen, den zugewiesenen Speicher freizugeben. Das Hauptanliegen, das entsteht, ist die Bestimmung ungenutzter Speicherressourcen.
Im Falle der Low-Level-Sprachen, bei denen der Entwickler manuell entscheiden muss, wann der Speicher nicht mehr benötigt wird, verwenden High-Level-Sprachen wie JavaScript eine automatisierte Form der Speicherverwaltung, die als Garbage Collection (GC) bekannt ist.
JavaScript verwendet zwei berühmte Strategien zur Durchführung von GC: die Reference-Counting-Technik und den Mark-and-Sweep-Algorithmus.
Hier ist eine detaillierte Erklärung von MDN zu beiden Algorithmen und wie sie funktionieren.

JIT (Just In Time) Kompilierung:

Kommen wir zurück zur JavaScript-Definition: Da steht „Interpretierte, JIT-kompilierte Programmiersprache“, also was bedeutet das überhaupt? Wie wäre es mit dem Unterschied zwischen einem Compiler und einem Interpreter im Allgemeinen?

Stellen Sie sich als Analogie zwei Menschen mit unterschiedlichen Sprachen vor, die kommunizieren möchten. Das Kompilieren ist, als würde man anhalten und sich die ganze Zeit nehmen, um die Sprache zu lernen, und das Dolmetschen ist, als hätte man jemanden, der jeden Satz interpretiert.

Kompilierte Sprachen haben also eine langsame Schreibzeit und eine schnelle Laufzeit und interpretierte Sprachen haben das Gegenteil.

Apropos Fachbegriffe: Beim Kompilieren wird der Quellcode eines Programms vor der Ausführung in maschinenlesbaren Binärcode umgewandelt, und ein Compiler nimmt das gesamte Programm auf einmal.

Andererseits ist ein Interpreter ein Programm, das die Programmanweisungen ausführt, ohne dass sie in ein maschinenlesbares Format vorkompiliert werden müssen, und es nimmt jeweils eine einzelne Codezeile.

Und hier kommt die JIT-Kompilierungsrolle, die die Leistung von interpretierten Programmen verbessert. Der gesamte Code wird auf einmal in Maschinencode umgewandelt und dann sofort ausgeführt .

Innerhalb des JIT-Compilers haben wir eine neue Komponente namens Monitor (auch bekannt als Profiler). Dieser Monitor überwacht den Code, während er ausgeführt wird, und

  • Identifizieren Sie die heißen oder warmen Komponenten des Codes, z. B.: sich wiederholender Code.
  • Übertragen Sie diese Komponenten während der Laufzeit in Maschinencode.
  • Optimieren Sie den generierten Maschinencode.
  • Tauschen Sie die vorherige Implementierung des Codes im laufenden Betrieb aus.

Nachdem wir nun die Kernkonzepte verstanden haben, nehmen wir uns eine Minute Zeit, um alles zusammenzufassen und die Schritte zusammenzufassen, die JS Engine beim Ausführen des Codes befolgt:

Bildquelle: Traversy Media Channel
  1. Die JS-Engine nimmt den in menschenlesbarer Syntax geschriebenen JS-Code und wandelt ihn in Maschinencode um.
  2. Die Engine verwendet einen Parser, um den Code Zeile für Zeile durchzugehen und zu prüfen, ob die Syntax korrekt ist. Wenn Fehler auftreten, wird die Ausführung des Codes angehalten und ein Fehler ausgegeben.
  3. Wenn alle Prüfungen erfolgreich sind, erstellt der Parser eine Baumdatenstruktur, die als Abstract Syntax Tree (AST) bezeichnet wird.
  4. Der AST ist eine Datenstruktur, die den Code in einer baumartigen Struktur darstellt. Es ist einfacher, Code von einem AST in Maschinencode umzuwandeln.
  5. Der Interpreter nimmt dann den AST und wandelt ihn in IR um, der eine Abstraktion des Maschinencodes und ein Vermittler zwischen JS-Code und Maschinencode ist. IR ermöglicht auch Optimierungen und ist mobiler.
  6. Der JIT-Compiler nimmt dann die generierte IR und wandelt sie in Maschinencode um, indem er den Code kompiliert, spontan Feedback erhält und dieses Feedback verwendet, um den Kompilierungsprozess zu verbessern.

Danke fürs Lesen :)

Sie können mir auf Twitter und LinkedIn folgen .