Das Ausführungsmodell von JavaScript ist differenziert und kann leicht missverstanden werden. Es kann hilfreich sein, sich über die Ereignisschleife im Kern zu informieren.

JavaScript ist eine Single-Threaded-Sprache, die darauf ausgelegt ist, Aufgaben einzeln zu erledigen. Die Ereignisschleife ermöglicht es JavaScript jedoch, Ereignisse und Rückrufe asynchron zu verarbeiten, indem gleichzeitige Programmiersysteme emuliert werden. Dies stellt die Leistung Ihrer JavaScript-Anwendungen sicher.

Was ist die JavaScript-Ereignisschleife?

Die Ereignisschleife von JavaScript ist ein Mechanismus, der im Hintergrund jeder JavaScript-Anwendung ausgeführt wird. Es ermöglicht JavaScript, Aufgaben der Reihe nach abzuwickeln, ohne seinen Hauptausführungsthread zu blockieren. Dies wird als bezeichnet asynchrone Programmierung.

Die Ereignisschleife verwaltet eine Warteschlange mit auszuführenden Aufgaben und führt diese Aufgaben nach rechts weiter Web-API zur Ausführung einzeln. JavaScript verfolgt diese Aufgaben und behandelt sie je nach Komplexitätsgrad der Aufgabe.

instagram viewer

Die Notwendigkeit der JavaScript-Ereignisschleife und der asynchronen Programmierung verstehen. Sie müssen verstehen, welches Problem es im Wesentlichen löst.

Nehmen Sie zum Beispiel diesen Code:

functionlongRunningFunction() {
// This function does something that takes a long time to execute.
for (var i = 0; i < 100000; i++) {
console.log("Hello")
}
}

functionshortRunningFunction(a) {
return a * 2 ;
}

functionmain() {
var startTime = Date.now();
longRunningFunction();

var endTime = Date.now();

// Prints the amount of time it took to execute functions
console.log(shortRunningFunction(2));
console.log("Time taken: " + (endTime - startTime) + " milliseconds");
}

main();

Dieser Code definiert zunächst eine aufgerufene Funktion longRunningFunction(). Diese Funktion erledigt eine komplexe zeitaufwändige Aufgabe. In diesem Fall führt es a aus für Schleife, die über 100.000 Mal iteriert. Das bedeutet, dass console.log("Hallo") läuft 100.000 Mal.

Abhängig von der Geschwindigkeit des Computers kann dies lange dauern und blockieren shortRunningFunction() von der sofortigen Ausführung bis zum Abschluss der vorherigen Funktion.

Zum Kontext finden Sie hier einen Vergleich der Zeit, die zum Ausführen beider Funktionen benötigt wurde:

Und dann die Single shortRunningFunction():

Der Unterschied zwischen einem 2.351-Millisekunden-Vorgang und einem 0-Millisekunden-Vorgang ist offensichtlich, wenn Sie eine leistungsstarke App erstellen möchten.

Wie die Ereignisschleife die App-Leistung verbessert

Die Ereignisschleife besteht aus verschiedenen Phasen und Teilen, die dazu beitragen, dass das System funktioniert.

Der Aufrufstapel

Der JavaScript-Aufrufstapel ist für die Art und Weise, wie JavaScript Funktions- und Ereignisaufrufe Ihrer Anwendung verarbeitet, von entscheidender Bedeutung. JavaScript-Code wird von oben nach unten kompiliert. Node.js weist jedoch beim Lesen des Codes Funktionsaufrufe von unten nach oben zu. Beim Lesen werden die definierten Funktionen nacheinander als Frames in den Aufrufstapel verschoben.

Der Aufrufstapel ist für die Aufrechterhaltung des Ausführungskontexts und der korrekten Reihenfolge der Funktionen verantwortlich. Dies geschieht durch den Betrieb als Last-In-First-Out (LIFO)-Stack.

Das bedeutet, dass der letzte Funktionsrahmen, den Ihr Programm auf den Aufrufstapel schiebt, als erster vom Stapel abspringt und ausgeführt wird. Dadurch wird sichergestellt, dass JavaScript die richtige Reihenfolge der Funktionsausführung beibehält.

JavaScript entfernt jeden Frame vom Stapel, bis er leer ist, was bedeutet, dass alle Funktionen vollständig ausgeführt wurden.

Libuv-Web-API

Der Kern der asynchronen Programme von JavaScript ist libuv. Die libuv-Bibliothek ist in der Programmiersprache C geschrieben, die mit dem Betriebssystem interagieren kann Low-Level-APIs. Die Bibliothek stellt mehrere APIs bereit, die die parallele Ausführung von JavaScript-Code ermöglichen Code. APIs zum Erstellen von Threads, eine API zur Kommunikation zwischen Threads und eine API zum Verwalten der Thread-Synchronisierung.

Zum Beispiel, wenn Sie verwenden setTimeout in Node.js, um die Ausführung anzuhalten. Der Timer wird über libuv eingerichtet, das die Ereignisschleife verwaltet, um die Rückruffunktion auszuführen, sobald die angegebene Verzögerung verstrichen ist.

Wenn Sie Netzwerkoperationen asynchron ausführen, verarbeitet libuv diese Operationen ebenfalls nicht blockierend Dadurch wird sichergestellt, dass andere Aufgaben mit der Verarbeitung fortfahren können, ohne auf den Ein-/Ausgabevorgang (E/A) warten zu müssen Ende.

Die Rückruf- und Ereigniswarteschlange

In der Rückruf- und Ereigniswarteschlange warten Rückruffunktionen auf ihre Ausführung. Wenn ein asynchroner Vorgang von der libuv abgeschlossen wird, wird die entsprechende Rückruffunktion dieser Warteschlange hinzugefügt.

So läuft die Sequenz ab:

  1. JavaScript verschiebt asynchrone Aufgaben zur Bearbeitung nach libuv und fährt sofort mit der Bearbeitung der nächsten Aufgabe fort.
  2. Wenn die asynchrone Aufgabe abgeschlossen ist, fügt JavaScript seine Rückruffunktion zur Rückrufwarteschlange hinzu.
  3. JavaScript führt so lange andere Aufgaben im Aufrufstapel aus, bis alles in der aktuellen Reihenfolge erledigt ist.
  4. Sobald der Aufrufstapel leer ist, schaut sich JavaScript die Rückrufwarteschlange an.
  5. Befindet sich ein Rückruf in der Warteschlange, wird der erste Rückruf auf den Aufrufstapel verschoben und ausgeführt.

Auf diese Weise blockieren asynchrone Aufgaben den Hauptthread nicht und die Rückrufwarteschlange stellt sicher, dass die entsprechenden Rückrufe in der Reihenfolge ausgeführt werden, in der sie abgeschlossen wurden.

Der Ereignisschleifenzyklus

Die Ereignisschleife verfügt auch über eine sogenannte Mikrotask-Warteschlange. Diese spezielle Warteschlange in der Ereignisschleife enthält Mikrotasks, deren Ausführung geplant ist, sobald die aktuelle Aufgabe im Aufrufstapel abgeschlossen ist. Diese Ausführung erfolgt vor der nächsten Rendering- oder Ereignisschleifeniteration. Mikrotasks sind Aufgaben mit hoher Priorität, die in der Ereignisschleife Vorrang vor regulären Aufgaben haben.

Bei der Arbeit mit Promises wird üblicherweise eine Mikrotask erstellt. Immer wenn ein Versprechen gelöst oder abgelehnt wird, ist es entsprechend .Dann() oder .fangen() Rückrufe werden in die Mikrotask-Warteschlange aufgenommen. Mithilfe dieser Warteschlange können Sie Aufgaben verwalten, die unmittelbar nach dem aktuellen Vorgang ausgeführt werden müssen, z. B. das Aktualisieren der Benutzeroberfläche Ihrer Anwendung oder das Behandeln von Statusänderungen.

Zum Beispiel eine Webanwendung, die den Datenabruf durchführt und die Benutzeroberfläche basierend auf den abgerufenen Daten aktualisiert. Benutzer können diesen Datenabruf auslösen, indem sie wiederholt auf eine Schaltfläche klicken. Jeder Tastenklick initiiert einen asynchronen Datenabrufvorgang.

Ohne Mikrotasks würde die Ereignisschleife für diese Aufgabe wie folgt funktionieren:

  1. Der Benutzer klickt wiederholt auf die Schaltfläche.
  2. Jeder Tastenklick löst einen asynchronen Datenabrufvorgang aus.
  3. Sobald die Datenabrufvorgänge abgeschlossen sind, fügt JavaScript die entsprechenden Rückrufe zur regulären Aufgabenwarteschlange hinzu.
  4. Die Ereignisschleife beginnt mit der Verarbeitung von Aufgaben in der regulären Aufgabenwarteschlange.
  5. Das UI-Update basierend auf den Ergebnissen des Datenabrufs wird ausgeführt, sobald die regulären Aufgaben dies zulassen.

Bei Mikrotasks funktioniert die Ereignisschleife jedoch anders:

  1. Der Benutzer klickt wiederholt auf die Schaltfläche und löst einen asynchronen Datenabrufvorgang aus.
  2. Sobald die Datenabrufvorgänge abgeschlossen sind, fügt die Ereignisschleife die entsprechenden Rückrufe zur Mikrotask-Warteschlange hinzu.
  3. Die Ereignisschleife beginnt mit der Verarbeitung von Aufgaben in der Mikrotask-Warteschlange unmittelbar nach Abschluss der aktuellen Aufgabe (Klick auf die Schaltfläche).
  4. Das auf den Datenabrufergebnissen basierende UI-Update wird vor der nächsten regulären Aufgabe ausgeführt und sorgt so für eine reaktionsschnellere Benutzererfahrung.

Hier ist ein Codebeispiel:

const fetchData = () => {
returnnewPromise(resolve => {
setTimeout(() => resolve('Data from fetch'), 2000);
});
};

document.getElementById('fetch-button').addEventListener('click', () => {
fetchData().then(data => {
// This UI update will run before the next rendering cycle
updateUI(data);
});
});

In diesem Beispiel ruft jeder Klick auf die Schaltfläche „Abrufen“ auf Daten abrufen(). Jeder Datenabrufvorgang wird als Mikrotask geplant. Basierend auf den abgerufenen Daten wird das UI-Update unmittelbar nach Abschluss jedes Abrufvorgangs ausgeführt, bevor andere Rendering- oder Ereignisschleifenaufgaben ausgeführt werden.

Dadurch wird sichergestellt, dass Benutzer die aktualisierten Daten sehen, ohne dass es zu Verzögerungen aufgrund anderer Aufgaben in der Ereignisschleife kommt.

Der Einsatz von Mikrotasks in solchen Szenarien kann UI-Ruckler verhindern und für schnellere und reibungslosere Interaktionen in Ihrer Anwendung sorgen.

Auswirkungen der Ereignisschleife auf die Webentwicklung

Das Verständnis der Ereignisschleife und der Verwendung ihrer Funktionen ist für die Erstellung leistungsfähiger und reaktionsfähiger Anwendungen von entscheidender Bedeutung. Die Ereignisschleife bietet asynchrone und parallele Funktionen, sodass Sie komplexe Aufgaben in Ihrer Anwendung effizient bearbeiten können, ohne die Benutzererfahrung zu beeinträchtigen.

Node.js bietet alles, was Sie brauchen, einschließlich Web-Worker, um weitere Parallelität außerhalb des Hauptthreads von JavaScript zu erreichen.