GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||
GeezerVison is showing developer.mozilla.org — JavaScript-Ausführungsmodell - JavaScript | MDN Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.
JavaScript-AusführungsmodellDiese Seite stellt die grundlegende Infrastruktur der JavaScript-Laufzeitumgebung vor. Das Modell ist weitgehend theoretisch und abstrakt; plattform- oder implementierungsspezifische Details bleiben außen vor. Moderne JavaScript-Engines optimieren die beschriebenen Abläufe umfassend. Diese Seite dient als Referenz. Sie setzt voraus, dass Sie bereits mit den Ausführungsmodellen anderer Programmiersprachen wie C und Java vertraut sind. Sie bezieht sich häufig auf bestehende Konzepte aus Betriebssystemen und Programmiersprachen. Die Engine und die Host-UmgebungFür die Ausführung von JavaScript müssen zwei Softwarekomponenten zusammenarbeiten: die JavaScript-Engine und die Host-Umgebung. Die JavaScript-Engine implementiert die Sprache ECMAScript (JavaScript) und stellt deren Kernfunktionalität bereit. Sie nimmt Quellcode entgegen, analysiert ihn und führt ihn aus. Um jedoch mit der Außenwelt zu interagieren – etwa um eine sinnvolle Ausgabe zu erzeugen, auf externe Ressourcen zuzugreifen oder Mechanismen für Sicherheit und Leistung bereitzustellen –, benötigen wir zusätzliche, umgebungsspezifische Mechanismen der Host-Umgebung. Wenn JavaScript in einem Webbrowser ausgeführt wird, gehört beispielsweise das HTML-DOM zur Host-Umgebung. Node.js ist eine weitere Host-Umgebung, in der JavaScript serverseitig ausgeführt werden kann. Obwohl wir uns in dieser Referenz vor allem auf die in ECMAScript definierten Mechanismen konzentrieren, gehen wir gelegentlich auch auf Mechanismen der HTML-Spezifikation ein. Diese werden häufig von anderen Host-Umgebungen wie Node.js oder Deno nachgebildet. So lässt sich ein zusammenhängendes Bild des JavaScript-Ausführungsmodells im Web und darüber hinaus vermitteln. Ausführungsmodell von AgentsIn der JavaScript-Spezifikation wird jede eigenständige Ausführungseinheit für JavaScript als Agent bezeichnet. Ein Agent verwaltet die folgenden Strukturen für die Codeausführung:
Diese drei eigenständigen Datenstrukturen verwalten unterschiedliche Daten. Die Queue und den Stack erläutern wir in den folgenden Abschnitten genauer. Weitere Informationen dazu, wie Heap-Speicher zugewiesen und freigegeben wird, finden Sie unter Speicherverwaltung. Jeder Agent ist mit einem Thread vergleichbar. Beachten Sie, dass die zugrunde liegende Implementierung dafür nicht unbedingt einen tatsächlichen Betriebssystem-Thread verwendet. Ein Agent kann mehrere Realms besitzen, die jeweils genau einem globalen Objekt zugeordnet sind und synchron aufeinander zugreifen können. Daher müssen sie in einem einzigen Ausführungs-Thread laufen. Ein Agent hat außerdem ein einheitliches Speichermodell. Dieses legt unter anderem fest, ob Little-Endian verwendet wird, ob der Agent synchron blockiert werden kann und ob atomare Operationen lock-free sind. Im Web kann ein Agent einer der folgenden Typen sein:
Anders ausgedrückt: Jeder Worker erstellt einen eigenen Agent, während sich ein oder mehrere Fenster im selben Agent befinden können – typischerweise ein Hauptdokument und dessen iframes mit ähnlichem Ursprung. In Node.js gibt es ein vergleichbares Konzept namens worker threads. Das folgende Diagramm veranschaulicht das Ausführungsmodell von Agents: RealmsJeder Agent besitzt einen oder mehrere Realms. Jeder JavaScript-Code wird beim Laden einem Realm zugeordnet. Diese Zuordnung bleibt bestehen, selbst wenn der Code aus einem anderen Realm aufgerufen wird. Ein Realm umfasst folgende Informationen:
Im Web ist jedem Realm genau ein globales Objekt zugeordnet und umgekehrt. Das globale Objekt ist entweder ein Realms werden meist erwähnt, wenn es um die Identität globaler Objekte geht. Beispielsweise benötigen wir Methoden wie Stack und AusführungskontexteBetrachten wir zunächst die synchrone Codeausführung. Jeder Job beginnt mit dem Aufruf seines zugehörigen Callbacks. Der Code in diesem Callback kann Variablen erstellen, Funktionen aufrufen oder die Ausführung beenden. Jede Funktion muss ihre eigene Variablenumgebung und die Stelle verwalten, zu der sie zurückkehrt. Dazu benötigt der Agent einen Stack, auf dem er die Ausführungskontexte nachverfolgt. Ein Ausführungskontext, allgemein auch Stack Frame genannt, ist die kleinste Ausführungseinheit. Er verwaltet die folgenden Informationen:
Stellen Sie sich ein Programm vor, das aus einem einzigen Job mit folgendem Code besteht: js
Generatoren und erneuter EintrittEin vom Stack entfernter Frame ist nicht unbedingt endgültig verschwunden, denn manchmal muss die Ausführung dorthin zurückkehren. Betrachten Sie beispielsweise eine Generatorfunktion: js In diesem Fall erzeugt der Aufruf von Tail CallsEin in der Spezifikation definierter Mechanismus ist der Proper Tail Call (PTC). Ein Funktionsaufruf ist ein Tail Call, wenn die aufrufende Funktion nach dem Aufruf nichts weiter tut, als dessen Wert zurückzugeben: js In diesem Fall ist der Aufruf von js In der Praxis erschwert das Verwerfen des aktuellen Frames allerdings das Debugging: Wenn ClosuresEin weiteres interessantes Phänomen im Zusammenhang mit Variablengültigkeitsbereichen und Funktionsaufrufen sind Closures. Wenn eine Funktion erstellt wird, merkt sie sich intern auch die Variablen-Bindings des aktuell laufenden Ausführungskontexts. Diese Bindings können dadurch länger bestehen bleiben als der Ausführungskontext selbst. js Job-Queue und Event LoopEin Agent ist ein Thread. Das bedeutet, dass der Interpreter jeweils nur eine Anweisung auf einmal verarbeiten kann. Solange der gesamte Code synchron ist, stellt das kein Problem dar, weil die Ausführung stets fortgesetzt werden kann. Muss der Code jedoch eine asynchrone Aktion ausführen, kann er erst fortfahren, wenn diese Aktion abgeschlossen ist. Würde dadurch das gesamte Programm angehalten, wäre das allerdings schlecht für die Benutzererfahrung. JavaScript als Skriptsprache für das Web muss daher niemals blockieren. Deshalb wird der Code, der auf den Abschluss einer asynchronen Aktion reagiert, als Callback definiert. Dieser Callback bildet einen Job, der nach Abschluss der Aktion in eine Job-Queue eingereiht wird – in der HTML-Terminologie in einen Event Loop. Der Agent entnimmt der Queue jeweils einen Job und führt ihn aus. Während der Ausführung kann dieser Job weitere Jobs erzeugen, die am Ende der Queue eingereiht werden. Jobs können auch durch den Abschluss asynchroner Plattformmechanismen wie Timer, I/O und Events hinzukommen. Ein Job gilt als abgeschlossen, wenn der Stack leer ist. Anschließend wird der nächste Job aus der Queue entnommen. Jobs werden möglicherweise nicht mit einheitlicher Priorität entnommen: HTML-Event-Loops teilen Jobs beispielsweise in zwei Kategorien ein, Tasks und Microtasks. Microtasks haben eine höhere Priorität; die Microtask-Queue wird vollständig abgearbeitet, bevor ein Job aus der Task-Queue entnommen wird. Weitere Informationen finden Sie im Leitfaden zu HTML-Microtasks. Ist die Job-Queue leer, wartet der Agent, bis neue Jobs hinzugefügt werden. „Run-to-completion“Jeder Job wird vollständig verarbeitet, bevor ein anderer Job ausgeführt wird. Das erleichtert die Analyse eines Programms: Eine laufende Funktion kann nicht unterbrochen werden und wird vollständig ausgeführt, bevor anderer Code läuft, der die von ihr verwendeten Daten verändern könnte. Das unterscheidet sich beispielsweise von C. Dort kann eine Funktion, die in einem Thread läuft, vom Laufzeitsystem jederzeit angehalten werden, damit anderer Code in einem anderen Thread ausgeführt wird. Betrachten Sie beispielsweise folgenden Code: js In diesem Beispiel erstellen wir ein bereits erfülltes Promise. Daher wird jeder daran angehängte Callback sofort als Job eingeplant. Die beiden Callbacks scheinen eine Race Condition zu verursachen, tatsächlich ist die Ausgabe aber vollständig vorhersehbar: Ein Nachteil dieses Modells ist, dass die Webanwendung keine Benutzerinteraktionen wie Klicks oder Scrollen verarbeiten kann, wenn ein Job zu lange dauert. Der Browser begegnet dem mit einem Dialog, der darauf hinweist, dass die Ausführung eines Skripts zu lange dauert. Es empfiehlt sich daher, Jobs kurz zu halten und einen Job nach Möglichkeit in mehrere Jobs aufzuteilen. Niemals blockierenEine weitere wichtige Eigenschaft des Event-Loop-Modells ist, dass die JavaScript-Ausführung niemals blockiert. I/O wird üblicherweise über Events und Callbacks verarbeitet. Während die Anwendung auf das Ergebnis einer IndexedDB-Abfrage oder einer Damit die Ausführung „niemals blockiert“, muss die Plattform-API natürlich grundsätzlich asynchron sein. Es gibt jedoch einige ältere Ausnahmen, etwa Agent-Cluster und gemeinsame SpeichernutzungMehrere Agents können über gemeinsam genutzten Speicher kommunizieren und bilden dadurch einen Agent-Cluster. Agents gehören genau dann zum selben Cluster, wenn sie Speicher gemeinsam nutzen können. Agents in unterschiedlichen Clustern können dennoch über andere Protokolle wie Beim Erstellen eines Agents, etwa durch das Starten eines Workers, bestimmen bestimmte Kriterien, ob er zum selben Cluster wie der aktuelle Agent gehört oder ein neuer Cluster erstellt wird. Die folgenden Paare globaler Objekte befinden sich beispielsweise jeweils im selben Agent-Cluster und können daher Speicher gemeinsam nutzen:
Die folgenden Paare globaler Objekte befinden sich nicht im selben Agent-Cluster und können daher keinen Speicher gemeinsam nutzen:
Den genauen Algorithmus finden Sie in der HTML-Spezifikation. Kommunikation zwischen Agents und SpeichermodellWie bereits erwähnt, können Agents über gemeinsam genutzten Speicher kommunizieren. Im Web wird Speicher über die Methode Es gibt zwei Möglichkeiten, auf gemeinsam genutzten Speicher zuzugreifen: durch normale Speicherzugriffe, die nicht atomar sind, und durch atomare Speicherzugriffe. Letztere sind sequenziell konsistent. Das bedeutet, dass eine strenge, vollständige Reihenfolge der Ereignisse besteht, über die alle Agents im Cluster übereinstimmen. Normale Speicherzugriffe sind dagegen ungeordnet; für sie besteht keine solche Reihenfolge. JavaScript stellt keine Operationen mit anderen Reihenfolgegarantien bereit. Die Spezifikation gibt Programmierenden, die mit gemeinsam genutztem Speicher arbeiten, folgende Empfehlungen:
Nebenläufigkeit und Sicherstellung des FortschrittsWenn mehrere Agents zusammenarbeiten, gilt die Garantie des niemals blockierenden Ablaufs nicht immer. Ein Agent kann blockiert beziehungsweise angehalten werden, während er darauf wartet, dass ein anderer Agent eine Aktion ausführt. Dies unterscheidet sich vom Warten auf ein Promise innerhalb desselben Agents: Dabei wird der gesamte Agent angehalten und in der Zwischenzeit kann kein anderer Code in ihm ausgeführt werden. Anders ausgedrückt: Der Agent kann keinen Fortschritt erzielen. Um Deadlocks zu vermeiden, gelten strenge Einschränkungen dafür, wann und welche Agents blockiert werden dürfen.
Der Agent-Cluster gewährleistet ein gewisses Maß an Integrität hinsichtlich der Aktivität seiner Agents, wenn diese durch äußere Einflüsse angehalten oder beendet werden:
Spezifikationen
Siehe auch
Help improve MDN | ||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |