Analyse des Ereignisschleifenmechanismus von JavaScript

Analyse des Ereignisschleifenmechanismus von JavaScript

Vorwort:

Dieses Mal werde ich hauptsächlich mein Verständnis des Js-Ereignisschleifenmechanismus, der Synchronisierung, der asynchronen Aufgaben, der Makroaufgaben und der Mikroaufgaben klären. Es besteht eine hohe Wahrscheinlichkeit, dass vorerst noch einige Abweichungen oder Fehler auftreten. Wenn ja, können Sie meine Fehler gerne korrigieren!

1. Gründe für die Ereignisschleife und die Aufgabenwarteschlange:

Erstens ist JS ein Single-Thread, daher ist dieses Design sinnvoll. Stellen Sie sich vor, der DOM wird auf einer Seite gelöscht und auf der anderen Seite hinzugefügt. Wie soll der Browser damit umgehen?

Zitat:

Single-Threaded bedeutet, dass die Aufgaben seriell ausgeführt werden und die nächste Aufgabe warten muss, bis die vorherige Aufgabe ausgeführt wurde, was zu einer langen Wartezeit führen kann. Aufgrund von Aufgaben wie Ajax-Netzwerkanforderungen, SetTimeout-Zeitverzögerungen und Benutzerinteraktionen mit DOM-Ereignissen verbrauchen diese Aufgaben jedoch keine CPU und sind eine Verschwendung von Ressourcen. Daher tritt Asynchronität auf. Durch die Zuweisung von Aufgaben zur Verarbeitung an entsprechende asynchrone Module wird die Effizienz des Hauptthreads erheblich verbessert und andere Vorgänge können parallel verarbeitet werden. Wenn die asynchrone Verarbeitung abgeschlossen ist und der Hauptthread inaktiv ist, liest der Hauptthread den entsprechenden Rückruf und führt nachfolgende Vorgänge aus, um die CPU-Nutzung zu maximieren. Zu diesem Zeitpunkt treten die Konzepte der synchronen Ausführung und der asynchronen Ausführung auf. Synchrone Ausführung bedeutet, dass der Hauptthread Aufgaben nacheinander und seriell ausführt; asynchrone Ausführung bedeutet, dass die CPU das Warten überspringt und nachfolgende Aufgaben zuerst verarbeitet (die CPU führt Aufgaben parallel mit Netzwerkmodulen, Timern usw. aus). Dadurch werden Aufgabenwarteschlangen und Ereignisschleifen erstellt, um die Arbeit zwischen dem Hauptthread und den asynchronen Modulen zu koordinieren.

zwei, Ereignisschleifenmechanismus:

Diagramm:

Bildbeschreibung hier einfügen

Zunächst wird die JS-Ausführungscodeoperation主線程und任務隊列unterteilt. Die Ausführung eines beliebigen JS-Codes kann in die folgenden Schritte unterteilt werden:

Schritt 1: Der Hauptthread liest den JS-Code, der eine synchrone Umgebung darstellt, und bildet den entsprechenden Heap und Ausführungsstapel.
Schritt 2: Wenn der Hauptthread auf eine asynchrone Operation stößt, übergibt er die asynchrone Operation zur Verarbeitung an die entsprechende API.
Schritt 3: Wenn der asynchrone Vorgang abgeschlossen ist, schieben Sie ihn in die Aufgabenwarteschlange. Schritt 4: Nachdem der Hauptthread ausgeführt wurde, fragen Sie die Aufgabenwarteschlange ab, nehmen Sie eine Aufgabe heraus und schieben Sie sie zur Verarbeitung in den Hauptthread. Schritt 5: Wiederholen Sie die Schritte 2, 3 und 4.

Zu den üblichen asynchronen Vorgängen zählen: Ajax-Anfragen, „setTimeout“ und ähnliche Onclik-Ereignisse usw.

drei, Aufgabenwarteschlange:

Synchrone und asynchrone Aufgaben gelangen jeweils in unterschiedliche Ausführungsumgebungen. Synchrone Aufgaben gelangen in den Hauptthread, also den Hauptausführungsstapel, und asynchrone Aufgaben gelangen in die Aufgabenwarteschlange.

Zunächst einmal folgt es, wie der Name schon sagt, da es sich um eine Warteschlange handelt, FIFO Prinzip.

Wie im Diagramm oben gezeigt, gibt es mehrere Aufgabenwarteschlangen und ihre Ausführungsreihenfolge ist:

In derselben Aufgabenwarteschlange werden Aufgaben vom Hauptthread in der Reihenfolge der Warteschlange weggenommen.
Es gibt eine Priorität zwischen verschiedenen Aufgabenwarteschlangen, und die mit der höheren Priorität wird zuerst abgerufen (z. B. Benutzer-E/A).

3.1 Arten von Aufgabenwarteschlangen:

Die Aufgabenwarteschlange ist in宏任務(macrotask queue) und微任務(microtask queue) unterteilt

Zu den Makroaufgaben gehören hauptsächlich: Skript (gesamter Code), setTimeout, setInterval, I/O, UI-Interaktionsereignisse, setImmediate (Node.js-Umgebung)

Zu den Mikrotasks gehören hauptsächlich: Promise, MutaionObserver, process.nextTick (Node.js-Umgebung)

3.2 Der Unterschied zwischen den beiden:

microtask queue:

(1) Eindeutig, es gibt nur eines in der gesamten Ereignisschleife;
(2) Die Ausführung erfolgt synchron. Mikrotasks in derselben Ereignisschleife werden nacheinander in der Warteschlangenreihenfolge ausgeführt.

PS: Daher kann die Mikrotask-Warteschlange eine synchrone Ausführungsumgebung bilden

Makrotask- macrotask queue :

(1) Nicht eindeutig, es gibt eine bestimmte Priorität (der Benutzer-E/A-Teil hat eine höhere Priorität)
(2) Asynchrone Ausführung: In derselben Ereignisschleife wird nur ein Ereignis ausgeführt.

3.3 Ein detaillierterer Event-Loop-Prozess

  • 1. 2. 3. Wie oben
  • Der Hauptthread fragt die Aufgabenwarteschlange ab, führt die Mikrotask-Warteschlange aus, führt sie der Reihe nach aus und schließt alle Ausführungen ab.
  • Der Hauptthread fragt die Aufgabenwarteschlange ab, führt die Makrotask-Warteschlange aus, nimmt die erste Aufgabe in der Warteschlange und führt sie aus.
  • Wiederholen Sie die Schritte 4 und 5.

Lassen Sie uns zur Vertiefung unseres Verständnisses ein einfaches Beispiel verwenden:

console.log('1, time = ' + new Date().toString()) // 1. Rufen Sie den Hauptthread auf, führen Sie Synchronisierungsaufgaben aus und geben Sie 1 aus
setTimeout(macroCallback, 0) // 2. Treten Sie der Makro-Task-Warteschlange bei // 7. Starten Sie die Ausführung dieses Timer-Makro-Tasks, rufen Sie macroCallback auf und geben Sie 4 aus
new Promise(function (resolve, reject) { //3. Treten Sie der Microtask-Warteschlange bei console.log('2, time = ' + new Date().toString()) //4. Führen Sie den Synchronisierungscode in diesem Microtask aus und geben Sie 2 aus
  lösen()
  console.log('3, time = ' + new Date().toString()) //5. Ausgabe 3
}).then(microCallback) // 6. Führe den then-Microtask aus, rufe microCallback auf und gebe 5 aus

//Funktionsdefinition Funktion macroCallback() {
  Konsole.log('4, Zeit = ' + neues Datum().toString())
}

Funktion MikroCallback() {
  Konsole.log('5, Zeit = ' + neues Datum().toString())
}

Laufergebnisse:

Bitte fügen Sie eine Beschreibung des Bildes hinzu

Vier, Leistungsstarker asynchroner Experte process.nextTick()

Als ich dieses Ding zum ersten Mal sah, kam es mir bekannt vor. Ich dachte darüber nach und es schien, als hätte ich this.$nextTick(callback) schon einmal im Vue-Projekt verwendet. Damals hieß es, dass der Code in der Rückruffunktion ausgeführt würde, nachdem die Elemente auf der Seite neu gerendert wurden. Ich habe es nicht ganz verstanden, also werde ich es mir vorerst einfach merken.

Bitte fügen Sie eine Beschreibung des Bildes hinzu

4.1 Wann wird process.nextTick() aufgerufen?

Immer wenn process.nextTick() während einer bestimmten Phase aufgerufen wird, werden alle an process.nextTick() übergebenen Rückrufe aufgelöst, bevor die Ereignisschleife fortgesetzt wird.

In der Ereignisschleife wird jeder Schleifenvorgang tick bezeichnet. Wenn Sie dies wissen, ist es einfacher zu verstehen, wann diese Methode aufgerufen wird!

Lassen Sie uns die Beispiele anderer Leute übernehmen, um unser Verständnis der Ereignisschleife zu vertiefen:

var flag = false // 1. Variablendeklaration Promise.resolve().then(() => {
  // 2. Verteile die then-Aufgabe an die Mikrotask-Warteschlange dieser Schleifenrunde console.log('then1') // 8. Führe die then-Mikrotask aus, drucke then1 aus und das Flag ist zu diesem Zeitpunkt wahr flag = true
})
neues Versprechen(lösen => {
  // 3. Führen Sie den synchronen Code in Promise aus console.log('promise')
  lösen()
  setTimeout(() => { // 4. Setze die Aufgabe im Timer in die Warteschlange der Makroaufgaben console.log('timeout2') // 11. Führe die Timer-Makroaufgabe aus. Hier ist eine Wartezeit von 10 angegeben, sodass sie nach einer anderen Timeraufgabe ausgeführt wird}, 10)
}).dann(Funktion () {
  // 5. Verteile die then-Aufgabe an die Microtask-Warteschlange dieser Schleifenrunde console.log('then2') // 9. Führe die then-Microtask aus und drucke then2 aus. Diese Tick-Runde endet})
Funktion f1(f) {
  // 1. Funktionsdeklaration f()
}
Funktion f2(f) {
  // 1. Funktionsdeklaration setTimeout(f) // 7. Setze „f“ in „setTimeout“ in die Warteschlange der Makroaufgabe ein, warte auf die Ausführung der aktuellen Runde von „tick“ und führe es dann in der nächsten Ereignisschleife aus}
f1(() => console.log('f ist:', flag ? 'asynchronous' : 'synchronous')) // 6. Drucken Sie `f ist: synchronous`
f2(() => {
  console.log('timeout1,', 'f is:', flag ? 'asynchronous' : 'synchronous') // 10. Timer-Makro-Task ausführen})

console.log('Diese Runde der Makroaufgaben wurde ausgeführt') // 7. Drucken

Laufergebnisse:

Bitte fügen Sie eine Beschreibung des Bildes hinzu

Der Rückruf in process.nextTick wird aufgerufen, nachdem der aktuelle Tick ausgeführt wurde und bevor die nächste Makroaufgabe ausgeführt wird.

Offizielles Beispiel:

lass bar;

// Diese Methode verwendet eine asynchrone Signatur, ruft die Rückruffunktion aber tatsächlich synchron auf someAsyncApiCall(callback) { callback(); }

// Rückruffunktion wird aufgerufen, bevor `someAsyncApiCall` abgeschlossen istsomeAsyncApiCall(() => {
  // bar wird kein Wert zugewiesen, da `someAsyncApiCall` abgeschlossen ist console.log('bar', bar); // undefiniert
});

Balken = 1;

Verwenden Sie process.nextTick :

lass bar;

Funktion someAsyncApiCall(Rückruf) {
  Prozess.nextTick(Rückruf);
}

einigeAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

Balken = 1;

Schauen wir uns ein weiteres Beispiel mit process.nextTick an:

console.log('1'); // 1. In den Ausführungsstapel des Hauptthreads einfügen und 1 ausgeben

setTimeout(function () { //2. Seine Rückruffunktion wird zur Warteschlange der Makroaufgaben hinzugefügt //7. Derzeit ist die Warteschlange der Mikroaufgaben leer. Nehmen Sie daher das erste Element aus der Warteschlange der Makroaufgaben heraus und führen Sie diese Aufgabe aus console.log('2'); // Ausgabe 2
    process.nextTick(function () { // 16. Die letzte Schleife endet, aufgerufen, bevor die nächste Makroaufgabe startet, Ausgabe 3
        konsole.log('3'); 
    })
    neues Versprechen (Funktion (Auflösung) {
    	//8. Führen Sie die Synchronisierungsaufgabe dieses Versprechens aus, geben Sie 4 aus, und der Status wird aufgelöst
        konsole.log('4');
        lösen();
    }).then(function () {//9. Erkenne dann die asynchrone Methode und füge ihre Rückruffunktion zur Microtask-Warteschlange hinzu console.log('5'); // 10. Nimm das erste Element in der Microtask-Warteschlange heraus, das der Rückruf davon ist, führe es aus und gib 5 aus
    })
})

process.nextTick(function () { // 11. Sobald die Ereignisschleife endet, führen Sie den Rückruf von nextTick() aus und geben Sie 6 aus
    konsole.log('6');
})
neues Versprechen (Funktion (Auflösung) { 
	//3. Führen Sie die synchrone Aufgabe im Versprechen aus und geben Sie 7 aus. Der Status ändert sich in „Aufgelöst“.
    konsole.log('7');
    lösen();
}).then(function () { //4. Erkenne dann die asynchrone Methode und füge ihre Rückruffunktion zur Microtask-Warteschlange hinzu console.log('8'); //6. Der Hauptthread wird ausgeführt, nimmt das erste Element aus der Microtask-Warteschlange, schiebt seine Rückruffunktion in den Ausführungsstapel und gibt 8 aus
})

setTimeout(function () { //5. Seine Rückruffunktion wird zur Warteschlange der Makroaufgabe hinzugefügt //12. Zu diesem Zeitpunkt ist die Warteschlange der Mikroaufgabe leer und die Makroaufgabe wird ausgeführt console.log('9'); // Ausgabe 9
    process.nextTick(function () { // 17. Zu diesem Zeitpunkt sind sowohl die Warteschlangen für Mikro- als auch für Makroaufgaben leer, die Schleife endet automatisch, führt diesen Rückruf aus und gibt 10 aus
        konsole.log('10');
    })
    neues Versprechen (Funktion (Auflösung) {
    	//13. Führen Sie die Synchronisierungsaufgabe dieses Versprechens aus, geben Sie 11 aus und der Status ändert sich console.log('11');
        lösen();
    }).then(function () {//14. Erkennen Sie die asynchrone Methode then und fügen Sie sie der Microtask-Warteschlange hinzu console.log('12');//15. Nehmen Sie das erste Element aus der Microtask-Warteschlange, führen Sie diese then-Mikrotask aus und geben Sie 12 aus
    })

})

Laufergebnisse:

Bitte fügen Sie eine Beschreibung des Bildes hinzu

Dieser Vorgang wird im Detail erklärt:

  • Rufen Sie zuerst den Hauptthread auf, erkennen Sie, dass das Protokoll nur eine normale Funktion ist, schieben Sie es in den Ausführungsstapel und geben Sie 1 aus.
  • Es wird erkannt, dass setTimeout eine spezielle asynchrone Methode (Makrotask) ist, und es wird zur Verarbeitung an andere Kernelmodule übergeben. Die Rückruffunktion von setTimeout wird in宏任務(macrotask) Warteschlange gestellt.
  • Es wird erkannt, dass das Promise-Objekt und die Resolve-Methode allgemeine Methoden sind, und seine Synchronisierungsaufgabe wird in den Ausführungsstapel verschoben, 7 wird ausgegeben und der Status wird in „Aufgelöst“ geändert.
  • Es wird erkannt, dass die then-Methode des Promise-Objekts eine asynchrone Methode ist, und sie wird zur Verarbeitung an andere Kernelmodule übergeben. Die Rückruffunktion wird in微任務(microtask) Warteschlange gestellt.
  • Ein weiteres setTimeout wird als spezielle asynchrone Methode erkannt und seine Rückruffunktion wird in宏任務(macrotask) Warteschlange gestellt.
  • An diesem Punkt ist der Hauptthread leer und beginnt, aus der Aufgabenwarteschlange zu entnehmen. Er nimmt das erste Element in der Mikrotask-Warteschlange heraus, bei dem es sich um den Rückruf der then-Methode des ersten Versprechens handelt, und führt es aus, wobei 8 ausgegeben wird;
  • Überprüfen Sie, ob die Mikrotask-Warteschlange zu diesem Zeitpunkt leer ist, nehmen Sie das erste Element der Makrotask-Warteschlange heraus, bei dem es sich um das erste setTimeOut handelt, führen Sie dessen Rückruffunktion aus und geben Sie 2 aus.
  • Bei seinem Rückruf stößt es auf ein Versprechen, führt seine synchrone Aufgabe aus, gibt 4 aus und der Status ändert sich;
  • Erkennen Sie es dann und fügen Sie es wie oben zur Mikrotask-Warteschlange hinzu.
  • Nehmen Sie das erste Element aus der Mikrotask-Warteschlange heraus und führen Sie es im Hauptthread aus, also gerade jetzt, und geben Sie 5 aus.
  • Diese Schleife endet, und bevor die nächste Makroaufgabe startet, wird der Rückruf des ersten process.nextTick() aufgerufen und die Ausgabe ist 6;
  • Starten Sie die nächste Makroaufgabe, nehmen Sie das erste Element in der Makroaufgabenwarteschlange heraus, bei dem es sich um den Rückruf des zweiten setTimeout handelt, verschieben Sie es in den Ausführungsstapel und geben Sie 9 aus.
  • Schieben Sie dann die Synchronisierungsaufgabe des Promise-Objekts in den Ausführungsstapel, geben Sie 11 aus und ändern Sie den Status in „Aufgelöst“.
  • Zu diesem Zeitpunkt wird die asynchrone Then-Methode erneut erkannt und ihr Rückruf wie oben zur Mikrotask-Warteschlange hinzugefügt.
  • Nehmen Sie das erste Element der Mikrotask-Warteschlange heraus, bei dem es sich gerade um den Rückruf handelt, und geben Sie 12 aus.
  • Diese Schleife endet und wird ausgeführt, bevor die nächste Makroaufgabe gestartet wird. Der Rückruf von process.nextTick() gibt 3 aus;
  • Zu diesem Zeitpunkt wird festgestellt, dass die Task-Warteschlange und der Hauptthread leer sind, die Ereignisschleife wird automatisch beendet und der letzte Rückruf process.nextTick() wird ausgeführt und gibt 10 aus.

Beenden! Wenn mir die Inspiration kommt, schreibe ich sie schnell auf und schaue später, ob es Probleme gibt. Sie können mich auch gerne auf meine Fehler hinweisen.

Lassen Sie uns ein einfaches Beispiel analysieren:

konsole.log('0');
setzeTimeout(() => {
    konsole.log('1');
    neues Versprechen(Funktion(Auflösung) {
        konsole.log('2');
        lösen();
    }).dann(()=>{
        konsole.log('3');
    })
    neues Versprechen(lösen => {
        konsole.log('4');
        für (lass i = 0; i < 9; i++) {
            i == 7 und lösen();
        }
        konsole.log('5');
    }).then(() => {
        konsole.log('6');
    })
})
  • Rufen Sie den Hauptthread auf, erkennen Sie, dass das Protokoll eine normale Funktion ist, schieben Sie es in den Ausführungsstapel und geben Sie 0 aus.
  • Es wird festgestellt, dass setTimeOut eine spezielle asynchrone Methode ist, die zur Verarbeitung an andere Module übergeben wird und deren Rückruffunktion der Makrotask-Warteschlange hinzugefügt wird.
  • Zu diesem Zeitpunkt gibt es keine Aufgaben im Hauptthread. Beginnen Sie also damit, Aufgaben aus der Aufgabenwarteschlange zu übernehmen.
  • Wenn festgestellt wird, dass die Aufgabenwarteschlange leer ist, wird das erste Element der Makroaufgabenwarteschlange herausgenommen. Dabei handelt es sich gerade um die Rückruffunktion des Timers.
  • Führen Sie die Synchronisierungsaufgabe aus und geben Sie 1 aus.
  • Erkennen Sie, dass es sich bei dem Versprechen und seiner Auflösungsmethode um allgemeine Methoden handelt, schieben Sie sie in den Ausführungsstapel, geben Sie 2 aus und ändern Sie den Status in „Auflösen“.
  • Erkennen Sie, dass die Then-Methode dieses Promises eine asynchrone Methode ist, und fügen Sie ihre Rückruffunktion zur Microtask-Warteschlange hinzu.
  • Dann wird ein weiteres Versprechen erkannt, die Synchronisierungsaufgabe wird ausgeführt, 4 und 5 werden ausgegeben und der Status ändert sich auf „aufgelöst“.
  • Fügen Sie dann die asynchrone Methode zur Mikrotask-Warteschlange hinzu.
  • Führen Sie das erste Element in der Mikrotask-Warteschlange aus, d. h. das „Third“ des ersten Versprechens, und geben Sie 3 aus.
  • Nehmen Sie dann das erste Element der Aufgabenwarteschlange heraus, das das „Ten“ des zweiten Versprechens ist, und geben Sie 6 aus.
  • Zu diesem Zeitpunkt sind sowohl der Hauptthread als auch die Aufgabenwarteschlange leer und die Ausführung ist abgeschlossen.

Ergebnisse der Codeausführung:

Bitte fügen Sie eine Beschreibung des Bildes hinzu

Bitte fügen Sie eine Beschreibung des Bildes hinzu

Dies ist das Ende dieses Artikels über die Analyse des Ereignisschleifenmechanismus von JavaScript. Weitere relevante Inhalte zum Ereignisschleifenmechanismus von JavaScript finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Ein Artikel erklärt den Ereignisschleifenmechanismus in JS
  • Lassen Sie uns kurz über den Ereignisschleifenmechanismus von JavaScript sprechen
  • Analyse des Ereignisschleifenmechanismus von js
  • Detaillierte Erläuterung des Ereignisschleifenmechanismus des JS-Browsers
  • Detaillierte Erklärung des JavaScript-Ereignisschleifenmechanismus
  • Beispielanalyse des JS-Ereignisschleifenmechanismus
  • Detailliertes Beispiel des Ereignisschleifenmechanismus in JS

<<:  Detaillierte Untersuchung, ob bei der MySQL-Fuzzy-Abfrage zwischen Groß- und Kleinschreibung unterschieden wird

>>:  Beispiel für die Einstellung des HTML-Hyperlink-Stils (vier verschiedene Zustände)

Artikel empfehlen

Detaillierte Erklärung der React-Ereignisbindung

1. Was ist In react Anwendungen werden Ereignisna...

So mounten Sie eine Festplatte in Linux

Wenn Sie eine virtuelle Maschine verwenden, stell...

Details der MySQL-Berechnungsfunktion

Inhaltsverzeichnis 2. Feldverkettung 2. Geben Sie...

Beispielcode zum Mischen von Float und Margin in CSS

Bei meinen letzten Studien habe ich einige Layout...

Detaillierte Erklärung zur Verwendung des Basis-Tags in HTML

In requireJS gibt es eine Eigenschaft namens base...

Ubuntu 16.04 mysql5.7.17 öffnet Remote-Port 3306

Aktivieren Sie den Remotezugriff auf MySQL MySQL-...

Meta Viewport ermöglicht die Vollbildanzeige von Webseiten auf dem iPhone

In meiner Verzweiflung dachte ich plötzlich: Wie i...