JavaScript unter der Haube: Ereignisschleife

Dec 01 2022
Lassen Sie uns JavaScript meistern, indem wir seine Funktionsweise von Grund auf erforschen. Sind Sie schon einmal mit undefinierten Fehlern konfrontiert oder hatten Mühe, den Gültigkeitsbereich einer Variablen zu identifizieren? Es ist wirklich zeitaufwändig, Fehler zu debuggen, ohne zu wissen, wie der Code funktioniert. In diesem Blog werde ich demonstrieren, wie fortschrittliche Konzepte wie Ereignisschleifen in Bezug auf Ausführungskontext, Call-Stack und Callback-Warteschlange tatsächlich funktionieren.

Lassen Sie uns JavaScript beherrschen, indem wir seine Funktionsweise von Grund auf untersuchen

Foto von Marc St auf Unsplash

Sind Sie jemals mit undefinierten Fehlern konfrontiert oder hatten Sie Schwierigkeiten, den Gültigkeitsbereich einer Variablen zu identifizieren?

Es ist wirklich zeitaufwändig, Fehler zu debuggen, ohne zu wissen, wie der Code funktioniert.

In diesem Blog werde ich demonstrieren, wie fortschrittliche Konzepte wie event loopin Bezug auf execution context, call stack, und callback queuetatsächlich funktionieren.

Ein Haftungsausschluss – Die Konzepte sind unglaublich wissensintensiv und miteinander verbunden, also bitte nicht einmal mit den Augen blinzeln!

JavaScript-Engine

Die V8-Engine von Google ist ein bekanntes Beispiel für eine JavaScript-Engine. Beispielsweise verwenden Chrome und Node.js beide die V8-Engine. Grundsätzlich besteht der V8-Motor aus zwei Teilen —

  1. Call Stack: Der gesamte Code wird darin ausgeführt. Es funktioniert wie die Stack-Datenstruktur, dh nach dem LIFO-Konzept (Last In First Out).
  2. Memory Heap: Wo die Speicherzuweisung erfolgt. Im Gegensatz zur Heap-Datenstruktur ist es nur ein Speicher.

Der Global Execution Context oder GEC ist der standardmäßige Ausführungskontext, der von der JavaScript-Engine erstellt wird, wenn eine Skriptdatei empfangen wird.

Jeglicher JavaScript-Code, der sich nicht in einer Funktion befindet, wird in ausgeführt GEC. Die folgenden Schritte werden ausgeführt in GEC

  • Konstruieren Sie einen Speicherplatz, um alle Variablen und Funktionen auf globaler Ebene zu speichern
  • Generieren Sie ein globales Objekt.
  • Generieren Sie das Schlüsselwortthis

Je nachdem, wo Ihr Code ausgeführt wird, wird bestimmt, wo thissich befindet. Beispielsweise zeigt es in Node.js auf ein bestimmtes globales Objekt, während es im Browser auf das windowObjekt zeigt.

Browser-Konsole

Aufrufstapel (oder Funktionsausführungsstapel)

JavaScript ist single-threaded, wie Sie bereits gehört haben. Aber was bedeutet es eigentlich?

Dies bedeutet, dass eine JavaScript-Engine nur eine call stackoder enthält function execution stack.

  • Wie wir wissen, wird die JS-Engine jedes Mal, wenn der Compiler Ihren Code zum ersten Mal durchsucht, vom Compiler aufgefordert, ein Global Execution Contextoder zu erstellen GECund es in die Call Stack.
  • Ihr gesamter Code wird nacheinander in ausgeführt Global Execution Contextund weist Speicher für die Funktionsdefinition oder Variablendeklaration zu und speichert ihn dort.
  • Aber wenn ein Funktionsaufruf gefunden wird, wird ein Functional Execution Contextoder FECerstellt, um den Code der Funktion auszuführen, und dann wird es am Anfang der call stack.
  • Der Interpreter entfernt eine Funktion call stackjedes Mal, wenn die Funktion beendet wird. Eine Funktion wird beendet – wenn sie das Ende ihres Gültigkeitsbereichs oder eine return-Anweisung erreicht.
  • Schließlich wird die Ausführung Ihres gesamten Codes GECaus der Call Stack.

Keine Sorge, wir demonstrieren es an einem Beispiel.

function f1{
  console.log('f1');
}

f1();
console.log('end');

Schritt 2: — In unserem Beispiel wird die erste Zeile ausgeführt, die f1. Ein Speicher wird für f1seine Definition zugewiesen und gespeichert.

Schritt 3: — In der 2. Zeile wird eine Funktion aufgerufen. Für diesen Funktionsaufruf wird ein Function Execution Context oder FECerstellt und über dem gespeichert Call Stack.

Schritt 4: — Jetzt wird f1()das Ganze Zeile für Zeile ausgeführt und nach Abschluss der Ausführung aus der entfernt Call Stack.

Schritt 5: — Dann wird die letzte Zeile console.log('end')ausgeführt und 'end' auf der Konsole ausgegeben. Schließlich wird die Ausführung Ihres gesamten Codes Global Execution Contextaus der Call Stack.

Wie verwaltet JS asynchrone Aufgaben?

JavaScript, wie wir alle bekannt sind, ist eine synchrone Singlethread- Sprache (jeweils eine Aufgabe), und der einzelne Thread, der ausgeführt wird, führt call stacksofort alles aus, was darin enthalten ist.

Aber was ist, wenn wir nach 5 Sekunden etwas ausführen müssen? Können wir das drinnen anziehen call stack?

Nein, können wir nicht. Weil call stackes keinen Timer hat. Aber wie können wir das tun?

Hier kommt die JavaScript-Laufzeit ins Spiel.

JavaScript-Laufzeitumgebung

Es kann untröstlich sein, wenn ich Ihnen jetzt sage, dass setTimeout()es kein Teil von JavaScript ist, obwohl alle console.log()DOM - Ereignisse die Teile von Web-APIs sind , die den Zugriff auf die JavaScript-Engine ermöglichen , um alle ihre Eigenschaften im Global Execution Context (GEC) zu verwenden das globale Objekt window. Diese Web-APIs werden als asynchron bezeichnet .

Was ist die Rückrufwarteschlange?

Eine Warteschlange von Aufgaben, die als „Rückrufwarteschlange“ oder „Aufgabenwarteschlange“ bezeichnet wird, ist eine Warteschlange, die ausgeführt wird, nachdem die aktuellen Aufgaben des Aufrufstapels abgeschlossen wurden. Aufgaben, die in der Web-API registriert sind, werden von der Web-API in die Callback-Warteschlange verschoben.

Die Callback-Warteschlange funktioniert wie eine Warteschlangen-Datenstruktur, was bedeutet, dass Aufgaben in FIFO-Reihenfolge (first in, first out) behandelt werden, im Gegensatz zu einem Aufrufstapel, was bedeutet, dass Aufgaben in der Reihenfolge behandelt werden, in der sie der Warteschlange hinzugefügt wurden.

Warteschlange für Rückrufe

Was ist eine Ereignisschleife?

Eine JavaScript-Ereignisschleife fügt dem Aufrufstapel in FIFO-Reihenfolge eine Aufgabe aus der Callback-Warteschlange hinzu, sobald der Aufrufstapel leer ist.

Die Ereignisschleife wird blockiert, wenn der Aufrufstapel derzeit Code ausführt und keine zusätzlichen Aufrufe aus der Warteschlange hinzufügt, bis der Stapel wieder leer ist. Dies liegt daran, dass JavaScript-Code in einer Run-to-Completion-Methode ausgeführt wird.

Lassen Sie uns die obigen Konzepte anhand eines Beispiels verstehen.

  • Zunächst Global Execution Contextwird unser Code in unserem erstellt call stackund GECunser Code Zeile für Zeile ausgeführt.
  • GECführt die erste Zeile aus und gibt 'Start' auf unserer Konsole aus.
  • Beim Ausführen der zweiten Zeile wird setTimeout()die Web-API aufgerufen und setTimeout()gibt dann den Zugriff auf die Timer-Funktion. Dann können Sie 5000mseine Verzögerungszeit einstellen.
  • Wenn Sie die callBack()Funktion über die übergeben setTimeout(), callBack()wird die als Rückruf über die Web-Web-API registriert.
  • Und GECführt dann die erste Zeile aus und gibt 'End' auf der Konsole aus.
  • Wenn der gesamte Code ausgeführt wurde, GECwird er aus unserer entfernt call stack.
  • Danach 5000 millisecondwird die callBack()Funktion, die in registriert ist, in die web APIverschoben call Back Queue.
  • Event loopsetzt die callBack()Funktion in die call StackWenn es fertig ist, ist es alles Arbeit. Und schließlich wird die callBack()Funktion ausgeführt und 'Call Back' in die Konsole ausgegeben.

function f1() {
    console.log('f1');
}

function f2() {
    console.log('f2');
}

function main() {
    console.log('main');
    
    setTimeout(f1, 0);
    
    f2();
}

main();

Wenn Sie denken, dass „f1“ vor „f2“ gedruckt wird, dann liegen Sie falsch. Es wird sein -

main
f2
f1

Der besprochene Mechanismus von JavaScript eignet sich für jede Callback-Funktion oder API-Anfrage.

Lassen Sie uns Schritt für Schritt beobachten, wie das zweite Beispiel in der Laufzeitumgebung funktioniert.

  1. Zuerst GECwird in erstellt call stackund dann Code Zeile für Zeile in ausgeführt GEC. Es speichert alle Funktionsdefinitionen im Memory Heap .
  2. Wenn der main()aufgerufen wird, Function Execution Context (FEC)wird a erstellt und gelangt dann in den Aufrufstapel. Danach main()wird der gesamte Code der Funktion Zeile für Zeile ausgeführt.
  3. Es hat ein Konsolenprotokoll, um das Wort main zu drucken. Das wird also console.log('main')ausgeführt und geht aus dem Stack.
  4. Die setTimeout()Browser-API findet statt. Die Callback-Funktion stellt es in die Callback-Warteschlange. Aber im Stack erfolgt die Ausführung wie gewohnt, gelangt also f2()in den Stack. Das Konsolenprotokoll von f2()executes. Beide gehen aus dem Stapel.
  5. Und dann main()springt der auch aus dem Stapel.
  6. Die Ereignisschleife erkennt, dass der Call-Stack leer ist und es eine Callback-Funktion in der Warteschlange gibt. Die Callback-Funktion f1()wird also von der event loop. Die Ausführung beginnt. Das Konsolenprotokoll wird ausgeführt und erscheint f1()auch aus dem Stack. Und schließlich befindet sich nichts anderes im Stapel und in der Warteschlange, um weiter ausgeführt zu werden.

Es ist meine Praxis und Notiz. Wenn Sie es nützlich fanden, zeigen Sie bitte Ihre Unterstützung, indem Sie unten auf das Klatschen-Symbol klicken. Du kannst mir auf Medium folgen .