Heim  >  Artikel  >  Web-Frontend  >  Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen

Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen

黄舟
黄舟Original
2017-02-25 13:47:391305Durchsuche

Vor einem Jahr habe ich einen Artikel geschrieben: „Was ist eine Ereignisschleife?“ ", sprach über mein Verständnis von Event Loop.

Letzten Monat habe ich zufällig die Rede von Philip Roberts „Hilfe, ich stecke in einer Ereignisschleife fest“ gesehen. Erst dann wurde mir beschämt klar, dass mein Verständnis falsch war. Ich habe beschlossen, diese Frage umzuschreiben, um das Innenleben der JavaScript-Engine detailliert, vollständig und korrekt zu beschreiben. Unten ist meine Neufassung.

Bevor Sie den Haupttext eingeben, fügen Sie eine Nachricht ein. Mein neues Buch „Einführung in ECMAScript 6“ ist erschienen (Copyright-Seite, Innenseite 1, Innenseite 2). Es ist in Farbe auf gestrichenem Papier gedruckt und sehr schön. Es enthält auch ein Index (natürlich der Preis). ist etwas teurer als vergleichbare Bücher). Zur Vorschau und zum Kauf klicken Sie hier.

Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen

1. Warum ist JavaScript Single-Threaded?

Ein Hauptmerkmal der JavaScript-Sprache ist, dass sie Single-Threaded ist, was bedeutet, dass sie jeweils nur eine Sache ausführen kann. Warum kann JavaScript also nicht mehrere Threads haben? Dadurch kann die Effizienz verbessert werden.

Der einzelne Thread von JavaScript hängt mit seinem Zweck zusammen. Als Browser-Skriptsprache besteht der Hauptzweck von JavaScript darin, mit Benutzern zu interagieren und das DOM zu manipulieren. Dies legt fest, dass es nur Single-Threaded sein kann, da es sonst zu sehr komplexen Synchronisationsproblemen kommt. Angenommen, JavaScript verfügt über zwei Threads gleichzeitig. Ein Thread fügt Inhalt zu einem bestimmten DOM-Knoten hinzu und der andere Thread löscht den Knoten. Welchen Thread sollte der Browser in diesem Fall verwenden?

Um Komplexität zu vermeiden, ist JavaScript seit seiner Geburt Single-Threaded. Dies ist zum Kernmerkmal dieser Sprache geworden und wird sich auch in Zukunft nicht ändern.

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 sind es auch Es ist nicht gestattet, das DOM zu bedienen. Daher ändert dieser neue Standard nichts an der Single-Threaded-Natur von JavaScript.

2. Aufgabenwarteschlange

Ein einzelner Thread bedeutet, dass alle Aufgaben in die Warteschlange gestellt werden müssen und die nächste Aufgabe erst ausgeführt wird, wenn die vorherige Aufgabe abgeschlossen ist. Wenn die vorherige Aufgabe lange dauert, muss die nächste Aufgabe warten.

Wenn die Warteschlange auf einen großen Rechenaufwand zurückzuführen ist und die CPU zu beschäftigt ist, vergessen Sie es, aber oft ist die CPU im Leerlauf, weil das IO-Gerät (Eingabe- und Ausgabegerät) sehr langsam ist (z. B Wenn Ajax-Vorgänge aus dem Netzwerk gelesen werden (Daten abrufen), müssen Sie warten, bis die Ergebnisse vorliegen, bevor Sie fortfahren können.

Die Designer der JavaScript-Sprache haben erkannt, dass die CPU zu diesem Zeitpunkt das E/A-Gerät vollständig ignorieren, die wartenden Aufgaben anhalten und die späteren Aufgaben zuerst ausführen kann. Warten Sie, bis das E/A-Gerät das Ergebnis zurückgibt, gehen Sie dann zurück und fahren Sie mit der Ausführung der angehaltenen Aufgabe fort.

Daher verfügt JavaScript über zwei Ausführungsmethoden: Eine besteht darin, dass die CPU sie nacheinander ausführt, die vorherige Aufgabe beendet und dann die nächste Aufgabe ausführt, was als synchrone Ausführung bezeichnet wird die Wartezeit Bei langen Aufgaben werden die nachfolgenden Aufgaben zuerst verarbeitet. Dies wird als asynchrone Ausführung bezeichnet. Es ist Sache des Programmierers, zu entscheiden, welche Ausführungsmethode er verwenden möchte.

Im Einzelnen ist der Betriebsmechanismus der asynchronen Ausführung wie folgt. (Dasselbe gilt für die synchrone Ausführung, da sie als asynchrone Ausführung ohne asynchrone Aufgaben betrachtet werden kann.)

(1) Alle Aufgaben werden im Hauptthread ausgeführt und bilden einen Ausführungskontextstapel.

(2) Zusätzlich zum Hauptthread gibt es auch eine „Aufgabenwarteschlange“. Das System stellt die asynchronen Aufgaben in die „Aufgabenwarteschlange“ und führt dann die Folgeaufgaben weiter aus.

(3) Sobald alle Aufgaben im „Ausführungsstapel“ ausgeführt wurden, liest das System die „Aufgabenwarteschlange“. Wenn die asynchrone Aufgabe zu diesem Zeitpunkt den Wartezustand beendet hat, gelangt sie aus der „Aufgabenwarteschlange“ in den Ausführungsstapel und nimmt die Ausführung wieder auf.

(4) Der Hauptthread wiederholt weiterhin den dritten Schritt oben.

Das Bild unten ist ein schematisches Diagramm des Hauptthreads und der Aufgabenwarteschlange.

Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen

Solange der Hauptthread leer ist, wird die „Aufgabenwarteschlange“ gelesen. Dies ist der laufende Mechanismus von JavaScript. Dieser Vorgang wiederholt sich ständig.

3. Ereignisse und Rückruffunktionen

„Aufgabenwarteschlange“ ist im Wesentlichen eine Warteschlange von Ereignissen (kann auch als Warteschlange von Nachrichten verstanden werden, wenn das IO-Gerät eine Aufgabe abschließt). in der „Aufgabenwarteschlange“ Fügen Sie der „Warteschlange“ ein Ereignis hinzu, um anzuzeigen, dass die zugehörige asynchrone Aufgabe in den „Ausführungsstapel“ gelangen kann. Der Hauptthread liest die „Aufgabenwarteschlange“, was bedeutet, dass die darin enthaltenen Ereignisse gelesen werden.

Zu den Ereignissen in der „Aufgabenwarteschlange“ gehören neben IO-Geräteereignissen auch einige benutzergenerierte Ereignisse (z. B. Mausklicks, Seitenscrollen usw.). Solange die Rückruffunktion angegeben ist, werden diese Ereignisse bei ihrem Auftreten in die „Aufgabenwarteschlange“ eingegeben und warten auf das Lesen durch den Hauptthread.

Die sogenannte „Rückruffunktion“ ist der Code, der vom Hauptthread aufgehängt wird. Asynchrone Aufgaben müssen eine Rückruffunktion angeben. Wenn die asynchrone Aufgabe aus der „Aufgabenwarteschlange“ zum Ausführungsstapel zurückkehrt, wird die Rückruffunktion ausgeführt.

„Aufgabenwarteschlange“ ist eine First-In-First-Out-Datenstruktur. Die zuerst eingestuften Ereignisse werden zuerst an den Hauptthread zurückgegeben. Der Lesevorgang des Hauptthreads erfolgt grundsätzlich automatisch. Sobald der Ausführungsstapel gelöscht wird, kehrt das erste Ereignis in der „Aufgabenwarteschlange“ automatisch zum Hauptthread zurück. Aufgrund der später erwähnten „Timer“-Funktion muss der Hauptthread jedoch die Ausführungszeit überprüfen und bestimmte Ereignisse müssen zum angegebenen Zeitpunkt zum Hauptthread zurückkehren.

4. Ereignisschleife

Der Hauptthread liest Ereignisse aus der „Aufgabenwarteschlange“. Dieser Prozess ist zyklisch, daher wird der gesamte Betriebsmechanismus auch als Ereignisschleife bezeichnet.

Um die Ereignisschleife besser zu verstehen, schauen Sie sich bitte das Bild unten an (zitiert aus der Rede von Philip Roberts „Hilfe, ich stecke in einer Ereignisschleife fest“).

Event Loop

Im Bild oben werden bei laufendem Hauptthread ein Heap und ein Stack generiert. Der Code im Stack ruft verschiedene externe APIs auf. Aufgabe“ Verschiedene Ereignisse (Klicken, Laden, Fertig) werden zur Warteschlange hinzugefügt. Solange der Code im Stapel ausgeführt wird, liest der Hauptthread die „Aufgabenwarteschlange“ und führt die diesen Ereignissen entsprechenden Rückruffunktionen nacheinander aus.

Der Code im Ausführungsstapel wird immer ausgeführt, bevor die „Aufgabenwarteschlange“ gelesen wird. Schauen Sie sich das Beispiel unten an.

    var req = new XMLHttpRequest();
    req.open('GET', url);    
    req.onload = function (){};    
    req.onerror = function (){};    
    req.send();

Die req.send-Methode im obigen Code ist eine Ajax-Operation zum Senden von Daten an den Server. Es handelt sich um eine asynchrone Aufgabe, was bedeutet, dass erst nach allen Codes von Wenn das aktuelle Skript ausgeführt wird, liest das System die „Aufgabenwarteschlange“. Daher entspricht es dem folgenden Schreiben.

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};    
    req.onerror = function (){};

Das heißt, der Teil, der die Rückruffunktion angibt (onload und onerror), spielt vor oder nach der send()-Methode keine Rolle, da sie zu einem Teil von gehören Der Ausführungsstapel liest nach der Ausführung immer die „Aufgabenwarteschlange“.

5. Timer

Zusätzlich zum Platzieren asynchroner Aufgaben hat die „Aufgabenwarteschlange“ auch eine Funktion, das heißt, sie kann zeitgesteuerte Ereignisse platzieren, also angeben, wie viel Zeit ein bestimmter Code benötigt wird danach ausgeführt. Dies wird als „Timer“-Funktion bezeichnet, bei der es sich um Code handelt, der regelmäßig ausgeführt wird.

Die Timer-Funktion wird hauptsächlich durch die beiden Funktionen setTimeout() und setInterval() vervollständigt. Ihre internen Betriebsmechanismen sind genau die gleichen. Der Unterschied besteht darin, dass der von erstere angegebene Code einmal ausgeführt wird wird wiederholt ausgeführt. Im Folgenden wird hauptsächlich setTimeout () erläutert.

setTimeout() akzeptiert zwei Parameter, der erste ist die Rückruffunktion und der zweite ist die Anzahl der Millisekunden, um die die Ausführung verzögert wird.

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

Die Ausführungsergebnisse des obigen Codes sind 1, 3, 2, da setTimeout() die Ausführung der zweiten Zeile bis 1000 Millisekunden später verzögert.

Wenn der zweite Parameter von setTimeout() auf 0 gesetzt ist, bedeutet dies, dass nach der Ausführung des aktuellen Codes (der Ausführungsstapel wird geleert) die angegebene Rückruffunktion sofort ausgeführt wird (Intervall von 0 Millisekunden).

setTimeout(function(){console.log(1);}, 0);
console.log(2);

Das Ausführungsergebnis des obigen Codes ist immer 2,1, da das System erst nach Ausführung der zweiten Zeile die Rückruffunktion in der „Aufgabenwarteschlange“ ausführt. .

Der HTML5-Standard schreibt vor, dass der Mindestwert (kürzestes Intervall) des zweiten Parameters von setTimeout() nicht weniger als 4 Millisekunden betragen darf. Wenn er niedriger als dieser Wert ist, wird er automatisch erhöht. Zuvor haben ältere Browser das Mindestintervall auf 10 Millisekunden festgelegt.

Darüber hinaus werden diese DOM-Änderungen (insbesondere solche, die das erneute Rendern von Seiten beinhalten) normalerweise nicht sofort, sondern alle 16 Millisekunden ausgeführt. Derzeit ist der Effekt der Verwendung von requestAnimFrame () besser als der von setTimeout ().

Es ist zu beachten, dass setTimeout() das Ereignis nur in die „Aufgabenwarteschlange“ einfügt. Der Hauptthread muss warten, bis die Ausführung des aktuellen Codes (Ausführungsstapels) abgeschlossen ist, bevor der Hauptthread die Rückruffunktion ausführt gibt an. Wenn der aktuelle Code lange dauert, kann es lange dauern, sodass nicht garantiert werden kann, dass die Rückruffunktion zu dem von setTimeout () angegebenen Zeitpunkt ausgeführt wird.

6. Ereignisschleife von Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen

Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen ist ebenfalls eine Single-Threaded-Ereignisschleife, ihr Betriebsmechanismus unterscheidet sich jedoch von der Browserumgebung.

Bitte sehen Sie sich das Diagramm unten an (Autor @BusyRich).

Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen

Basierend auf dem Bild oben ist der Betriebsmechanismus von Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen wie folgt.

(1) Die V8-Engine analysiert JavaScript-Skripte.

(2) Der analysierte Code ruft die Node-API auf.

(3) Die libuv-Bibliothek ist für die Ausführung der Node-API verantwortlich. Es weist verschiedenen Threads unterschiedliche Aufgaben zu, um eine Ereignisschleife (Ereignisschleife) zu bilden, und gibt die Ausführungsergebnisse der Aufgaben asynchron an die V8-Engine zurück.

(4) Die V8-Engine gibt die Ergebnisse an den Benutzer zurück.

  Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen有一个process.nextTick()方法,可以将指定事件推迟到Event Loop的下一次执行,或者说放到"Ausführliche Erklärung des JavaScript-Funktionsmechanismus: Lassen Sie uns noch einmal über die Ereignisschleife sprechen"的头部,也就是当前的执行栈清空之后立即执行。

function foo() {
    console.error(1);
}

process.nextTick(foo);
console.log(2);
// 2
// 1

  process.nextTick(foo)的作用,与setTimeout(foo, 0)很相似,但是执行效率高得多。

 以上就是JavaScript 运行机制详解:再谈Event Loop的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn