Heim >Web-Frontend >Front-End-Fragen und Antworten >Technische Antwort: JavaScript-Ausführungsmechanismus
Dieser Artikel bringt Ihnen einige Probleme im Zusammenhang mit dem Ausführungsmechanismus in JavaScript, wir stoßen oft auf Situationen, in denen wir die Ausführungsreihenfolge des Codes kennen müssen.
Prozesse und Threads
Wir alle wissen, dass der Kern des Computers die CPU ist, die alle Rechenaufgaben übernimmt; und das Betriebssystem ist der Manager des Computers, der für die Aufgaben verantwortlich ist Planung und Ressourcenzuweisung und -verwaltung, die die gesamte Computerhardware steuern; Anwendungen sind Programme mit bestimmten Funktionen und Programme, die auf dem Betriebssystem ausgeführt werden.
Prozess
Ein Prozess ist ein dynamischer Ausführungsprozess eines Programms mit unabhängigen Funktionen für einen Datensatz. Er ist eine unabhängige Einheit für die Ressourcenzuweisung und -planung durch das Betriebssystem und der Trägerprozess für die Anwendung Laufen ist die kleinste Einheit, die Ressourcen besitzen und unabhängig laufen kann, und sie ist auch die kleinste Einheit für die Programmausführung.
Eigenschaften eines Prozesses:
Dynamik: Ein Prozess ist ein Ausführungsprozess eines Programms. Er ist temporär, hat einen Lebenszyklus, wird dynamisch generiert und stirbt.
Parallelität: Jeder Prozess kann sein gleichzeitig mit anderen Prozessen ausgeführt;
Unabhängigkeit: Der Prozess ist eine unabhängige Einheit des Systems zur Ressourcenzuweisung und -planung;
Struktur: Der Prozess besteht aus drei Teilen: Programm, Daten und Prozesssteuerungsblock.
Thread
Ein Thread ist ein einzelner sequenzieller Steuerungsprozess bei der Programmausführung. Er ist die kleinste Einheit des Programmausführungsflusses und die Grundeinheit der Prozessorplanung und -verteilung. Ein Prozess kann einen oder mehrere Threads haben, und jeder Thread teilt sich den Speicherplatz des Programms (dh den Speicherplatz des Prozesses). Ein Standard-Thread besteht aus Thread-ID, aktuellem Befehlszeiger (PC), Registern und Stapel. Der Prozess besteht aus Speicherplatz (Code, Daten, Prozessraum, geöffnete Dateien) und einem oder mehreren Threads.
Der Unterschied zwischen Prozess und Thread
Thread ist die kleinste Einheit der Programmausführung und Prozess ist die kleinste Einheit der Ressourcenzuweisung durch das Betriebssystem.
Ein Prozess besteht aus einem oder mehreren Threads und einem Thread ist der Code in einem Prozess. Verschiedene Ausführungsrouten.
Prozesse sind unabhängig voneinander, aber jeder Thread unter demselben Prozess teilt sich den Speicherplatz des Programms (einschließlich Codesegmente, Datensätze, Heaps usw.) und einige Prozesse -Ebenenressourcen (z. B. das Öffnen von Dateien und Signalen), Prozesse sind füreinander unsichtbar;
Planung und Wechsel: Der Thread-Kontextwechsel ist viel schneller als der Prozesskontextwechsel.
Warum ist JS Single-Threaded?
JavaScript wird seit seiner Geburt als Browser-Skriptsprache verwendet. Es wird hauptsächlich für die Benutzerinteraktion und den Betrieb von DOM verwendet. Dies bedeutet, dass es nur Single-Threaded sein kann, da es sonst zu sehr komplexen Synchronisierungsproblemen kommt.
Zum Beispiel: Wenn JS Multithreading ist, ein Thread ein DOM-Element ändern möchte und ein anderer Thread das DOM-Element löschen möchte, weiß der Browser nicht, auf wen er hören soll. Um Komplexität zu vermeiden, wurde JavaScript von Anfang an so konzipiert, dass es Single-Threaded ist.
Um die Rechenleistung von Multi-Core-CPUs zu nutzen, schlägt HTML5 den Web Worker-Standard vor, der es JavaScript-Skripten ermöglicht, mehrere Threads zu erstellen, die untergeordneten Threads werden jedoch vollständig vom Haupt-Thread gesteuert und dürfen das DOM nicht bedienen . Daher ändert dieser neue Standard nichts an der Single-Threaded-Natur von JavaScript
Browserprinzip
Als Front-End-Ingenieur müssen Sie mit Browsern vertraut sein, und Browser sind Multiprozess-Browser.
Browser-Komponenten
Benutzeroberfläche: enthält Adressleiste, Vorwärts/Zurück/Aktualisieren/Lesezeichen
Browser-Engine: überträgt Anweisungen zwischen der Benutzeroberfläche und der Rendering-Engine
Rendering-Engine: verwendet um den angeforderten Inhalt zu zeichnen
Netzwerk: wird zum Abschließen von Netzwerkaufrufen wie http-Anfragen verwendet, verfügt über eine plattformunabhängige Schnittstelle und kann auf verschiedenen Plattformen arbeiten
JavaScript-Interpreter: wird zum Parsen und Ausführen von JavaScript-Code verwendet
Benutzeroberflächen-Backend: Wird zum Zeichnen grundlegender Widgets wie Kombinationsfelder und Fenster verwendet.
Datenspeicher: Gehört zur Persistenzschicht und wird vom Browser gespeichert Auf der Festplatte Für verschiedene Daten, die Cookies ähneln, definiert HTML5 die Webdatenbanktechnologie, eine leichte und vollständige clientseitige Speichertechnologie.
Hinweis: Im Gegensatz zu den meisten Browsern entspricht jeder Browser von Google (Chrome) jeder Registerkarte eine Rendering-Engine-Instanz. Jede Registerkarte ist ein unabhängiger Prozess.
Welche Prozesse enthält der Browser? Verantwortlich für die Anzeige der Browseroberfläche und die Interaktion mit Benutzern. Wie vorwärts, rückwärts usw. Verantwortlich für die Verwaltung jeder Seite, das Erstellen und Zerstören anderer Prozesse Zeichnen Sie die Bitmap (Bitmap) in den Speicher, der durch den Rendering-Prozess (Renderer) zur Benutzeroberfläche erhalten wurde Verwaltung von Netzwerkressourcen, Downloads usw. Plug-in-Prozess von Drittanbietern Verantwortlich für die Verwaltung von Plug-ins von Drittanbietern GPU-Prozess Verantwortlich für 3D-Rendering und Hardwarebeschleunigung (höchstens eine) Rendering-Prozess Verantwortlich für das Parsen, Ausführen und Rendern von Seitendokumenten Welche Threads im Rendering-Prozess enthalten sind GUI-Rendering-Thread Hauptverantwortlich für Parsen von HTML, CSS, Erstellen eines DOM-Baums, Layout, Zeichnen usw. Dieser Thread ist mit der JavaScript-Engine verbunden. Threads schließen sich gegenseitig aus. Wenn der Thread der JavaScript-Engine ausgeführt wird, wird der GUI-Rendering-Thread angehalten Ist inaktiv, führt der Hauptthread das GUI-Rendering aus Der JavaScript-Engine-Thread ist hauptsächlich für die Verarbeitung von JavaScript-Skripten und die Ausführung von Code verantwortlich (z. B. V8-Engine) Der Browser kann nur einen JS-Engine-Thread haben, der den JS ausführt Programm gleichzeitig, das heißt, JS ist Single-Threaded Der JS-Engine-Thread und der GUI-Rendering-Thread schließen sich gegenseitig aus, sodass die JS-Engine das Seitenrendering blockiert Zeitgesteuerter Trigger-Thread Verantwortlich für die Ausführung des Timers Funktionen (setTimeout, setInterval) Browser-Timing-Zähler werden von der JS-Engine nicht gezählt (da JS Single-Threaded ist, wirkt sich eine Blockierung auf die Genauigkeit des Zählers aus) ) ) Zeit- und Trigger-Timing durch eine separate Thread (nachdem das Timing abgeschlossen ist, fügen Sie es der Ereigniswarteschlange des ereignisauslösenden Threads hinzu und warten Sie auf die Ausführung, nachdem die JS-Engine inaktiv ist. Dieser Thread ist der Timing-Trigger-Thread, auch Timer-Thread genannt.)W3C Es ist in festgelegt Der HTML-Standard besagt, dass das Zeitintervall unter 4 ms in setTimeout als 4 ms gezählt wird Das entsprechende Ereignis wird am Ende der zu verarbeitenden Warteschlange hinzugefügt und wartet auf die Verarbeitung durch die JS-Engine. Synchronisierte Aufgaben Asynchrone Aufgaben Synchronisierte und asynchrone Aufgaben treten jeweils an unterschiedlichen Ausführungsplätzen auf, indem sie synchron in den Hauptthread und asynchron in die Ereignistabelle eintreten und registrieren Sie die Funktion. Nachdem die Aufgabe im Hauptthread ausgeführt wurde, wird sie in die Ereigniswarteschlange verschoben, um die entsprechende Funktion zu lesen und zur Ausführung in den Hauptthread einzutreten. Wir kommen nicht umhin zu fragen: Woher wissen wir, dass der Haupt-Thread-Ausführungsstapel leer ist? In der js-Engine gibt es einen Überwachungsprozess, der kontinuierlich prüft, ob der Haupt-Thread-Ausführungsstapel leer ist. Sobald er leer ist, wird er in die Ereigniswarteschlange verschoben, um zu prüfen, ob eine Funktion auf den Aufruf wartet.
Die Reihenfolge der Ereignisschleife bestimmt die Ausführungsreihenfolge des JS-Codes. Nach Eingabe des Gesamtcodes (Makrotask) startet der erste Zyklus. Führen Sie dann alle Mikrotasks aus. Beginnen Sie dann erneut mit der Makroaufgabe, suchen Sie eine der auszuführenden Aufgabenwarteschlangen und führen Sie dann alle Mikroaufgaben aus. Ausführungsstapel und Aufgabenwarteschlange JavaScript-Code wird in einem Ausführungskontext ausgeführt. Es gibt drei Ausführungskontexte in JavaScript: Globaler Ausführungskontext Funktionsausführungskontext wird erstellt Eval-Ausführungskontext, der von der Eval-Funktion generierte Kontext (weniger verwendet) Im Allgemeinen hat unser JS-Code mehr als einen Kontext. Wie ist also die Ausführungsreihenfolge dieser Kontexte? Wir alle wissen, dass der Stapel eine Last-In-First-Out-Datenstruktur ist. Der Ausführungsstapel in unserem JavaScript ist eine solche Stapelstruktur. Wenn die JS-Engine den Code ausführt, wird ein globaler Kontext generiert und in den Stapel verschoben Immer wenn ein Funktionsaufruf auftritt, wird der Funktionsausführungskontext generiert und auf den Ausführungsstapel verschoben. Die Engine beginnt mit der Ausführung der Funktion oben im Stapel und der Ausführungskontext wird nach der Ausführung angezeigt. Werfen wir einen Blick auf den Ausführungsstapel des obigen Codes: Aufgabenwarteschlange Wir haben bereits erwähnt, dass alle Aufgaben in JavaScript in synchrone Aufgaben und asynchrone Aufgaben, synchrone Aufgaben usw. unterteilt sind Wie der Name schon sagt, handelt es sich um eine Aufgabe, die sofort ausgeführt wird. Sie gelangt normalerweise direkt zur Ausführung in den Hauptthread. Unsere asynchrone Aufgabe tritt in die Aufgabenwarteschlange ein und wartet auf die Ausführung der Aufgabe im Hauptthread, bevor sie ausgeführt wird. Die Aufgabenwarteschlange ist eine Ereigniswarteschlange, die angibt, dass verwandte asynchrone Aufgaben in den Ausführungsstapel gelangen können. Der Hauptthread liest die Aufgabenwarteschlange, um die darin enthaltenen Ereignisse zu lesen. Queue ist eine First-In-First-Out-Datenstruktur. Wir haben oben erwähnt, dass asynchrone Aufgaben in Makroaufgaben und Mikroaufgaben unterteilt werden können, sodass Aufgabenwarteschlangen auch in Makroaufgabenwarteschlangen und Mikroaufgabenwarteschlangen unterteilt werden können Makroaufgabenwarteschlange: Für relativ große Arbeiten gehören zu den häufigsten Aufgaben setTimeout, setInterval, Benutzerinteraktion, UI-Rendering usw.; Microtask-Warteschlange: Führen Sie kleinere Aufgaben aus. Zu den häufigsten gehören Promise, Process.nextTick; Zur Ausführung werden sie in den Hauptthread gestellt, und asynchrone Aufgaben (Klickereignisse, Timer, Ajax usw.) werden im Hintergrund ausgeführt und warten auf den Abschluss von E/A-Ereignissen oder die Auslösung von Verhaltensereignissen. Asynchrone Aufgaben werden hier in Makroaufgaben und Mikroaufgaben unterteilt, die in die Makroaufgabenwarteschlange eingegeben werden, und Mikroaufgaben, die in die Mikroaufgabenwarteschlange eingegeben werden. Die Aufgaben in der Ausführungsaufgabenwarteschlange werden speziell im Ausführungsstapel ausgeführt. Wenn alle Aufgaben im Hauptthread ausgeführt werden, werden sie alle in der Mikroaufgabenwarteschlange ausgeführt Lesen Sie die Makroaufgaben. Der obige Vorgang wird kontinuierlich wiederholt, was wir oft als Ereignisschleife (Event-Loop) bezeichnen.
function add(){
console.log(1)
foo()
console.log(3)
}
function foo(){
console.log(2)
}
add()
(async ()=>{
console.log(1)
setTimeout(() => {
console.log('setTimeout1')
}, 0);
function foo (){
return new Promise((res,rej) => {
console.log(2)
res(3)
})
}
new Promise((resolve,reject)=>{
console.log(4)
resolve()
console.log(5)
}).then(()=> {
console.log('6')
})
const res = await foo();
console.log(res);
console.log('7')
setTimeout(_ => console.log('setTimeout2'))
})()
Der Code Beginne von oben. Als nächstes ausführen, zuerst auf console.log(1) stoßen, 1 direkt drucken, dann auf einen Timer stoßen, der zu einer Makroaufgabe gehört, und ihn in die Makroaufgabenwarteschlange stellen
Dann auf ein Versprechen stoßen, denn das neue Versprechen ist ein Synchrone Aufgabe, also drucken Sie 4 direkt aus. Wenn Sie auf „Resolve“ stoßen, bei dem es sich um die nachfolgende Then-Funktion handelt, stellen Sie sie in die Mikrotask-Warteschlange, drucken Sie 5
und führen Sie dann „await foo“ aus. Es gibt ein Versprechen in der foo-Funktion, und ein neues Versprechen ist Es handelt sich um eine synchrone Aufgabe, sodass 2 direkt gedruckt wird und „Warten“ einen Versprechensrückruf zurückgibt. Die Aufgabe nach „Warten“ wird in die Mikrotask-Warteschlange gestellt
Schließlich wird ein Timer angetroffen und in die Makrotask-Warteschlange gestellt
Die Ausführungsstapelaufgabe ist abgeschlossen Gehen Sie zuerst zur Mikrotask-Warteschlange, um die Mikrotask-Ausführung abzurufen, und führen Sie sie zuerst aus. Drucken Sie für die erste Mikrotask 6 aus und führen Sie dann die zweite Mikrotask aus. Drucken Sie 3, 7
Nachdem die Mikrotask ausgeführt wurde, gehen Sie zur Makrotask-Warteschlange, um sie abzurufen Makrotask-Ausführung, print setTimeout1, setTimeout2
[Verwandte Empfehlungen:
Javascript-Studien-Tutorial】Das obige ist der detaillierte Inhalt vonTechnische Antwort: JavaScript-Ausführungsmechanismus. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!