Heim  >  Artikel  >  Web-Frontend  >  Lassen Sie uns über die Prinzipien und Beispiele der JavaScript-Ereignisschleife sprechen

Lassen Sie uns über die Prinzipien und Beispiele der JavaScript-Ereignisschleife sprechen

WBOY
WBOYnach vorne
2022-11-10 17:27:492066Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über JavaScript, das hauptsächlich den relevanten Inhalt der Ereignisschleife vorstellt. Ich hoffe, er wird für alle hilfreich sein.

Lassen Sie uns über die Prinzipien und Beispiele der JavaScript-Ereignisschleife sprechen

【Verwandte Empfehlungen: JavaScript-Video-Tutorial, Web-Frontend

Das Verständnis der JavaScript-Ereignisschleife wird häufig von Makro- und Mikroaufgaben, einem JavaScript-Single-Thread-Ausführungsprozess und einem Browser begleitet Der asynchrone Mechanismus und andere damit zusammenhängende Probleme sowie die Implementierung von Ereignisschleifen in Browsern und NodeJS sind ebenfalls sehr unterschiedlich. Wenn wir mit der Ereignisschleife vertraut sind und den Betriebsmechanismus des Browsers verstehen, können wir den Ausführungsprozess von JavaScript verstehen und Probleme bei der Codeausführung beheben.

Das Prinzip der asynchronen Ausführung von Browser-JS

JS ist Single-Threaded, das heißt, es kann nur eine Sache gleichzeitig ausführen. Denken Sie also darüber nach: Warum kann der Browser gleichzeitig asynchrone Aufgaben ausführen?

Da der Browser über mehrere Threads verfügt, startet der Browser einen anderen Thread, um die Aufgabe auszuführen, wenn JS eine asynchrone Aufgabe ausführen muss. Mit anderen Worten: „JS ist Single-Threaded“ bedeutet, dass es nur einen Thread gibt, der JS-Code ausführt, nämlich den vom Browser bereitgestellten JS-Engine-Thread (Haupt-Thread). Es gibt auch Timer-Threads und HTTP-Anfrage-Threads im Browser. Diese Threads werden nicht hauptsächlich zum Ausführen von JS-Code verwendet.

Wenn Sie beispielsweise eine AJAX-Anfrage im Hauptthread senden müssen, wird diese Aufgabe an einen anderen Browser-Thread (HTTP-Anfrage-Thread) übergeben, um die Anfrage tatsächlich zu senden. Wenn die Anfrage zurückkommt, erfolgt der erforderliche JS-Rückruf Der im Rückruf auszuführende Aufruf wird zur Ausführung an den JS Engine-Thread übergeben. **Das heißt, der Browser ist derjenige, der tatsächlich die Aufgabe des Sendens der Anfrage ausführt, und JS ist nur für die Durchführung der endgültigen Rückrufverarbeitung verantwortlich. **Die Asynchronität wird hier also nicht von JS selbst implementiert, sondern ist tatsächlich die vom Browser bereitgestellte Fähigkeit.

Nehmen Sie Chrome als Beispiel. Der Browser verfügt nicht nur über mehrere Threads, sondern auch über mehrere Prozesse, wie z. B. Rendering-Prozesse, GPU-Prozesse, Plug-In-Prozesse usw. Jede Registerkarte ist ein unabhängiger Rendering-Prozess. Wenn also eine Registerkarte abnormal abstürzt, sind andere Registerkarten grundsätzlich nicht betroffen. Als Front-End-Entwickler konzentrieren Sie sich hauptsächlich auf den Rendering-Prozess. Der Rendering-Prozess umfasst JS-Engine-Threads, HTTP-Anforderungs-Threads und Timer-Threads usw. Diese Threads bilden die Grundlage für JS, um asynchrone Aufgaben im Browser auszuführen.

Eine kurze Analyse ereignisgesteuerter Aufgaben

Hinter dem Ausführungsprinzip asynchroner Browseraufgaben verbirgt sich tatsächlich eine Reihe ereignisgesteuerter Mechanismen. Ereignisauslösung, Aufgabenauswahl und Aufgabenausführung werden alle durch ereignisgesteuerte Mechanismen erreicht. Das Design von NodeJS und Browsern ist ereignisgesteuert. Die Ereignisse können hier beispielsweise durch Benutzervorgänge ausgelöst werden. Der Timer-Thread im Browser löst das Timer-Ereignis aus, nachdem der Timer abgelaufen ist. Das Thema dieses Artikels Die Ereignisschleife ist eigentlich eine Reihe von Prozessen zum Verwalten und Ausführen von Ereignissen in einem ereignisgesteuerten Modell.

Nehmen Sie als Beispiel eine einfache Szene. Angenommen, es gibt eine Schaltfläche zum Verschieben und ein Charaktermodell auf der Spieloberfläche. Jedes Mal, wenn Sie zum Bewegen nach rechts klicken, muss die Position des Charaktermodells neu gerendert und nach rechts verschoben werden um 1 Pixel. Wir können es je nach Rendering-Timing auf unterschiedliche Weise implementieren.

Implementierungsmethode eins: ereignisgesteuert. Nach dem Klicken auf die Schaltfläche wird beim Ändern der KoordinatenpositionX sofort das Schnittstellen-Rendering-Ereignis ausgelöst und ein erneutes Rendern ausgelöst.

Implementierungsmethode zwei: staatsgesteuert oder datengesteuert. Nach dem Klicken auf die Schaltfläche wird nur die KoordinatenpositionX geändert und das Rendern der Schnittstelle wird nicht ausgelöst. Zuvor wird ein Timer setInterval gestartet oder requestAnimationFrame verwendet, um kontinuierlich zu erkennen, ob sich positionX ändert. Bei Änderungen sofort neu rendern.

Die Verarbeitung von Klickereignissen in Browsern erfolgt typischerweise ebenfalls ereignisgesteuert. Wenn in einem ereignisgesteuerten System ein Ereignis ausgelöst wird, werden die ausgelösten Ereignisse der Reihe nach vorübergehend in einer Warteschlange gespeichert. Nach Abschluss der JS-Synchronisierungsaufgabe werden die zu verarbeitenden Ereignisse aus dieser Warteschlange entnommen und verarbeitet. Wann also Aufgaben abgerufen werden sollen und welche Aufgaben zuerst abgerufen werden sollen, wird durch den Ereignisschleifenprozess gesteuert.

Ereignisschleife im Browser

Ausführungsstapel und Aufgabenwarteschlange

Wenn JS einen Codeabschnitt analysiert, ordnet es den Synchronisationscode irgendwo in der richtigen Reihenfolge an, nämlich im Ausführungsstapel, und führt dann die darin enthaltenen Funktionen nacheinander aus. Wenn eine asynchrone Aufgabe auftritt, wird sie zur Verarbeitung an andere Threads übergeben. Nachdem alle Synchronisierungscodes im aktuellen Ausführungsstapel ausgeführt wurden, werden die Rückrufe der abgeschlossenen asynchronen Aufgaben aus einer Warteschlange entnommen und dem Ausführungsstapel hinzugefügt Um die Ausführung fortzusetzen, wird eine asynchrone Aufgabe erneut verarbeitet und an andere Threads übergeben. Nachdem andere asynchrone Aufgaben abgeschlossen sind, wird der Rückruf in die Aufgabenwarteschlange gestellt, um zur Ausführung aus dem Ausführungsstapel entnommen zu werden.

JS führt die Methoden im Ausführungsstapel nacheinander aus. Jedes Mal, wenn eine Methode ausgeführt wird, wird eine eindeutige Ausführungsumgebung (Kontext) für diese Methode generiert. Nachdem die Ausführung dieser Methode abgeschlossen ist, wird die aktuelle Ausführungsumgebung zerstört Diese Methode wird vom Stapel entfernt (d. h. der Verbrauch ist abgeschlossen) und wird dann mit der nächsten Methode fortgesetzt.

Es ist ersichtlich, dass im ereignisgesteuerten Modus mindestens eine Ausführungsschleife enthalten ist, um zu erkennen, ob sich neue Aufgaben in der Aufgabenwarteschlange befinden. Durch die kontinuierliche Schleife, um asynchrone Rückrufe zur Ausführung herauszunehmen, ist dieser Prozess eine Ereignisschleife, und jede Schleife ist ein Ereigniszyklus oder ein Tick.

Makroaufgaben und Mikroaufgaben

Es gibt mehr als eine Aufgabenwarteschlange. Abhängig von der Art der Aufgabe kann sie in Mikroaufgabenwarteschlange und Makroaufgabenwarteschlange unterteilt werden.

Nach Abschluss der Ausführung des Synchronisationscodes prüft der Ausführungsstapel zunächst, ob Aufgaben in der Mikrotask-Warteschlange vorhanden sind, die ausgeführt werden müssen. Wenn nicht, wechseln Sie zur Überprüfung in die Makrotask-Warteschlange ob Aufgaben ausgeführt werden müssen usw. Mikrotasks werden im Allgemeinen zuerst im aktuellen Zyklus ausgeführt, während Makrotasks bis zum nächsten Zyklus warten. Daher werden Mikrotasks im Allgemeinen vor Makrotasks ausgeführt, und es gibt nur eine Mikrotask-Warteschlange, während es möglicherweise mehrere Makrotask-Warteschlangen gibt. Darüber hinaus gehören auch unsere üblichen Klick- und Tastaturereignisse zu den Makroaufgaben. Werfen wir einen Blick auf gängige Makroaufgaben und gängige Mikroaufgaben.

Allgemeine Makroaufgaben:

setTimeout()
  • setInterval()
  • setImmediate()
Allgemeine Mikroaufgaben:

promise.then(), Promise.catch()
  • neu MutaionObserver()
  • process.nextTick()
  • console.log('同步代码1');setTimeout(() => {    console.log('setTimeout')
    }, 0)new Promise((resolve) => {  console.log('同步代码2')  resolve()
    }).then(() => {    console.log('promise.then')
    })console.log('同步代码3');// 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"
  • Der obige Code wird in der folgenden Reihenfolge ausgegeben: „Sync-Code 1“, „Sync-Code 2“, „Sync-Code 3“, „promise.then“, „ setTimeout ", die spezifische Analyse ist wie folgt.

(1) Der setTimeout-Callback und Promise.then werden beide asynchron ausgeführt und nach dem gesamten synchronen Code ausgeführt.

Übrigens, wenn die setTimeout-Verzögerung im Browser auf 0 gesetzt ist, beträgt sie standardmäßig 4 ms , NodeJS ist 1 ms. Der genaue Wert kann variieren, ist aber nicht 0.

(2) Obwohl Promise.then am Ende geschrieben wird, liegt die Ausführungsreihenfolge vor setTimeout, da es sich um eine Mikrotask handelt.

(3) New Promise wird synchron ausgeführt und der Rückruf in Promise.then ist asynchron von.

Werfen wir einen Blick auf die Demonstration des Ausführungsprozesses des obigen Codes:

Manche Leute verstehen es auch so: Mikroaufgaben werden am Ende der aktuellen Ereignisschleife ausgeführt; Makroaufgaben werden am Anfang ausgeführt die nächste Ereignisschleife. Werfen wir einen Blick auf den wesentlichen Unterschied zwischen Mikrotasks und Makrotasks.

Wir wissen bereits, dass JS, wenn es auf eine asynchrone Aufgabe trifft, diese zur Verarbeitung an andere Threads übergibt und sein Hauptthread weiterhin synchrone Aufgaben ausführt. Beispielsweise wird das Timing von setTimeout vom Timer-Thread des Browsers verwaltet. Wenn das Timing endet, wird die Timer-Callback-Aufgabe in die Task-Warteschlange gestellt und wartet darauf, dass der Haupt-Thread sie zur Ausführung herausnimmt. Wir haben bereits erwähnt, dass zur Ausführung asynchroner Aufgaben andere Browser-Threads zur Unterstützung erforderlich sind, da JS in einem einzelnen Thread ausgeführt wird. Das heißt, Multithreading ist ein offensichtliches Merkmal asynchroner JS-Aufgaben.

Lassen Sie uns die Verarbeitung von Promise.then (Mikrotask) analysieren. Wenn Promise.then ausgeführt wird, übergibt die V8-Engine die asynchrone Aufgabe nicht an andere Browser-Threads, sondern speichert den Rückruf sofort nach Abschluss der Ausführung des aktuellen Ausführungsstapels Warteschlange, in der Promise.then gespeichert ist, beinhalten kein Multithreading. Selbst aus mancher Perspektive können Mikrotasks nicht vollständig asynchron sein. Sie ändern lediglich die Ausführungsreihenfolge des Codes.

setTimeout hat die Aufgabe, „das Warten zu timen“, was vom Timer-Thread ausgeführt werden muss; die Ajax-Anfrage hat die Aufgabe, „eine Anfrage zu senden“, die vom HTTP-Thread ausgeführt werden muss, während Promise.then dies tut Es gibt keine asynchronen Aufgaben, die von anderen Threads ausgeführt werden müssen, es gibt nur Rückrufe. Auch wenn dies der Fall ist, handelt es sich nur um eine weitere darin verschachtelte Makroaufgabe.

Eine kurze Zusammenfassung der wesentlichen Unterschiede zwischen Mikrotasks und Makrotasks.

  • Makroaufgabenfunktionen: Es müssen eindeutige asynchrone Aufgaben ausgeführt werden, und es sind Rückrufe für andere asynchrone Threads erforderlich.
  • Mikrotask-Funktionen: Es müssen keine klaren asynchronen Aufgaben ausgeführt werden, es sind nur Rückrufe erforderlich.

Timer-Fehler

In der Ereignisschleife wird immer zuerst der synchrone Code ausgeführt und dann der asynchrone Rückruf zur Ausführung aus der Aufgabenwarteschlange abgerufen. Wenn setTimeout ausgeführt wird, startet der Browser einen neuen Thread, um den Aufruf zu timen. Nach Ablauf des Timers wird das Timer-Ereignis ausgelöst und der Rückruf in der Makro-Task-Warteschlange gespeichert und wartet darauf, dass der JS-Hauptthread die Ausführung übernimmt. Wenn der Hauptthread zu diesem Zeitpunkt noch die Synchronisierungsaufgabe ausführt, muss die Makroaufgabe zu diesem Zeitpunkt zuerst angehalten werden, was zu einem ungenauen Timerproblem führt. Je länger der Synchronisationscode dauert, desto größer ist der Fehler im Timer. Nicht nur der Synchronisationscode, da die Mikrotasks zuerst ausgeführt werden, sondern auch das Timing. Wenn es eine Endlosschleife im Synchronisationscode gibt oder die Rekursion in der Mikrotask ständig andere Mikrotasks startet, dann der Code in der Makrotask darf niemals umgesetzt werden. Daher ist es sehr wichtig, die Ausführungseffizienz des Haupt-Thread-Codes zu verbessern.

Ein ganz einfaches Szenario ist, dass es auf unserer Schnittstelle eine Uhr gibt, die auf die Sekunde genau geht und die Zeit jede Sekunde aktualisiert. Sie werden feststellen, dass die Sekunden manchmal einfach das 2-Sekunden-Intervall überspringen, deshalb.

Rendern der Ansichtsaktualisierung

Nachdem die Ausführung der Mikrotask-Warteschlange abgeschlossen ist, dh nachdem eine Ereignisschleife endet, führt der Browser hier eine Browseroptimierung durch, und die Ergebnisse mehrerer Schleifen können auftreten zusammengeführt werden, um eine Ansichtsaktualisierung durchzuführen, sodass die Ansicht nach der Ereignisschleife aktualisiert wird, sodass nicht jede Operation auf dem Dom die Ansicht unbedingt sofort aktualisiert. Der requestAnimationFrame-Rückruf wird ausgeführt, bevor die Ansicht neu gezeichnet wird. Daher ist es umstritten, ob requestAnimationFrame eine Mikrotask oder eine Makrotask ist. Von hier aus sollte es sich weder um eine Mikrotask noch um eine Makrotask handeln.

Ereignisschleife in NodeJS

Die JS-Engine selbst implementiert den Ereignisschleifenmechanismus nicht. Die Ereignisschleife im Browser wird hauptsächlich vom Browser implementiert, und NodeJS verfügt auch über eine eigene Ereignisschleife . NodeJS verfügt außerdem über einen Schleifen- und Aufgabenwarteschlangenprozess und Mikrotasks haben Vorrang vor Makrotasks. Die allgemeine Leistung stimmt mit dem Browser überein. Es gibt jedoch auch einige Unterschiede zum Browser und es wurden einige neue Aufgabentypen und Aufgabenphasen hinzugefügt. Als nächstes stellen wir den Ereignisschleifenprozess in NodeJS vor.

Asynchrone Methoden in NodeJS

Da sie alle auf der V8-Engine basieren, sind die im Browser enthaltenen asynchronen Methoden auch in NodeJS identisch. Es gibt auch einige andere gängige Formen der Asynchronität in NodeJS.

  • Datei-E/A: Lokale Dateien asynchron laden.
  • setImmediate(): Ähnlich wie bei der setTimeout-Einstellung 0 ms wird es sofort ausgeführt, nachdem bestimmte Synchronisierungsaufgaben abgeschlossen sind.
  • process.nextTick(): Sofort ausführen, nachdem bestimmte Synchronisierungsaufgaben abgeschlossen sind.
  • server.close, socket.on('close',...) usw.: Rückruf schließen.

Stellen Sie sich vor, wenn das obige Formular gleichzeitig mit setTimeout, Promise usw. existiert, wie kann dann die Ausführungsreihenfolge des Codes analysiert werden? Solange wir den Ereignisschleifenmechanismus von NodeJS verstehen, wird es klar sein.

Ereignisschleifenmodell

Die plattformübergreifenden Funktionen und der Ereignisschleifenmechanismus von NodeJS basieren alle auf der Libuv-Bibliothek. Sie müssen sich nicht um den spezifischen Inhalt dieser Bibliothek kümmern. Wir müssen nur wissen, dass die Libuv-Bibliothek ereignisgesteuert ist und API-Implementierungen auf verschiedenen Plattformen kapselt und vereinheitlicht. In

NodeJS analysiert die V8-Engine den JS-Code und ruft die Node-API auf. Anschließend übergibt sie die Aufgabe zur Zuweisung an Libuv und gibt schließlich die Ausführungsergebnisse an die V8-Engine zurück. In Libux ist eine Reihe von Ereignisschleifenprozessen implementiert, um die Ausführung dieser Aufgaben zu verwalten, sodass die Ereignisschleife von NodeJS hauptsächlich in Libuv abgeschlossen wird.

Werfen wir einen Blick darauf, wie Schleifen in Libuv aussehen.

Jede Phase der Ereignisschleife

Bei der Ausführung von JS in NodeJS ist der Prozess, um den wir uns hauptsächlich kümmern müssen, in die folgenden Phasen unterteilt. Jede der folgenden Phasen verfügt über eine eigene separate Aufgabenwarteschlange. the current Gibt an, ob in der Aufgabenwarteschlange der Phase Aufgaben vorhanden sind, die verarbeitet werden müssen.

  • timers 阶段:执行所有 setTimeout() 和 setInterval() 的回调。
  • pending callbacks 阶段:某些系统操作的回调,如  TCP  链接错误。除了 timers、close、setImmediate 的其他大部分回调在此阶段执行。
  • poll 阶段:轮询等待新的链接和请求等事件,执行 I/O 回调等。V8 引擎将 JS 代码解析并传入 Libuv 引擎后首先进入此阶段。如果此阶段任务队列已经执行完了,则进入 check 阶段执行 setImmediate 回调(如果有 setImmediate),或等待新的任务进来(如果没有 setImmediate)。在等待新的任务时,如果有 timers 计时到期,则会直接进入 timers 阶段。此阶段可能会阻塞等待。
  • check 阶段:setImmediate 回调函数执行。
  • close callbacks 阶段:关闭回调执行,如 socket.on('close', ...)。

上面每个阶段都会去执行完当前阶段的任务队列,然后继续执行当前阶段的微任务队列,只有当前阶段所有微任务都执行完了,才会进入下个阶段。这里也是与浏览器中逻辑差异较大的地方,不过浏览器不用区分这些阶段,也少了很多异步操作类型,所以不用刻意去区分两者区别。代码如下所示:

const fs = require('fs');
fs.readFile(__filename, (data) => {    // poll(I/O 回调) 阶段
    console.log('readFile')    Promise.resolve().then(() => {        console.error('promise1')
    })    Promise.resolve().then(() => {        console.error('promise2')
    })
});setTimeout(() => {    // timers 阶段
    console.log('timeout');    Promise.resolve().then(() => {        console.error('promise3')
    })    Promise.resolve().then(() => {        console.error('promise4')
    })
}, 0);// 下面代码只是为了同步阻塞1秒钟,确保上面的异步任务已经准备好了var startTime = new Date().getTime();var endTime = startTime;while(endTime - startTime < 1000) {
    endTime = new Date().getTime();
}// 最终输出 timeout promise3 promise4 readFile promise1 promise2

另一个与浏览器的差异还体现在同一个阶段里的不同任务执行,在 timers 阶段里面的宏任务、微任务测试代码如下所示:

setTimeout(() => {  console.log('timeout1')    Promise.resolve().then(function() {    console.log('promise1')
  })
}, 0);setTimeout(() => {  console.log('timeout2')    Promise.resolve().then(function() {    console.log('promise2')
  })
}, 0);
  • 浏览器中运行

    每次宏任务完成后都会优先处理微任务,输出“timeout1”、“promise1”、“timeout2”、“promise2”。

  • NodeJS 中运行

    因为输出 timeout1 时,当前正处于  timers 阶段,所以会先将所有 timer 回调执行完之后再执行微任务队列,即输出“timeout1”、“timeout2”、“promise1”、“promise2”。

上面的差异可以用浏览器和 NodeJS 10 对比验证。是不是感觉有点反程序员?因此 NodeJS 在版本 11 之后,就修改了此处逻辑使其与浏览器尽量一致,也就是每个 timer 执行后都先去检查一下微任务队列,所以 NodeJS 11 之后的输出已经和浏览器一致了。

nextTick、setImmediate 和 setTimeout

实际项目中我们常用 Promise 或者 setTimeout 来做一些需要延时的任务,比如一些耗时计算或者日志上传等,目的是不希望它的执行占用主线程的时间或者需要依赖整个同步代码执行完成后的结果。

NodeJS 中的 process.nextTick() 和 setImmediate() 也有类似效果。其中 setImmediate() 我们前面已经讲了是在 check 阶段执行的,而 process.nextTick() 的执行时机不太一样,它比 promise.then() 的执行还早,在同步任务之后,其他所有异步任务之前,会优先执行 nextTick。可以想象是把 nextTick 的任务放到了当前循环的后面,与 promise.then() 类似,但比 promise.then() 更前面。意思就是在当前同步代码执行完成后,不管其他异步任务,先尽快执行 nextTick。如下面的代码,因此这里的 nextTick 其实应该更符合“setImmediate”这个命名才对。

setTimeout(() => {    console.log('timeout');
}, 0);Promise.resolve().then(() => {    console.error('promise')
})
process.nextTick(() => {    console.error('nextTick')
})// 输出:nextTick、promise、timeout

接下来我们再来看看 setImmediate 和 setTimeout,它们是属于不同的执行阶段了,分别是 timers 阶段和 check 阶段。

setTimeout(() => {  console.log('timeout');
}, 0);setImmediate(() => {  console.log('setImmediate');
});// 输出:timeout、 setImmediate

分析上面代码,第一轮循环后,分别将 setTimeout   和 setImmediate 加入了各自阶段的任务队列。第二轮循环首先进入  timers 阶段,执行定时器队列回调,然后  pending callbacks 和 poll 阶段没有任务,因此进入check 阶段执行 setImmediate 回调。所以最后输出为“timeout”、“setImmediate”。当然这里还有种理论上的极端情况,就是第一轮循环结束后耗时很短,导致 setTimeout 的计时还没结束,此时第二轮循环则会先执行 setImmediate 回调。

再看这下面一段代码,它只是把上一段代码放在了一个 I/O 任务回调中,它的输出将与上一段代码相反。

const fs = require('fs');
fs.readFile(__filename, (data) => {    console.log('readFile');    setTimeout(() => {        console.log('timeout');
    }, 0);    setImmediate(() => {        console.log('setImmediate');
    });
});// 输出:readFile、setImmediate、timeout

如上面代码所示:

  • Es gibt keine asynchrone Aufgabenwarteschlange, die in der ersten Runde der Schleife ausgeführt werden muss.
  • Es gibt keine Aufgaben in den Timern und anderen Phasen der zweiten Runde der Schleife, nur die Abfragephase hat E/A-Rückrufaufgaben. was „readFile“ ausgibt;
  • Beziehen Sie sich auf die vorherige Ereignisphase. Beachten Sie, dass als nächstes die Abfragephase erkennt, ob eine setImmediate-Aufgabenwarteschlange vorhanden ist, und ansonsten beurteilt wird, ob ein Timer-Aufgabenrückruf vorliegt , es kehrt zur Timer-Phase zurück, um setImmediate auszuführen und „setImmediate“ auszugeben. Dann geht es in die letzte Abschluss-Callback-Phase über.
  • Fahren Sie schließlich mit der dritten Runde fort Schleife, betreten Sie die Timer-Phase und geben Sie „timeout“ aus.
  • Die endgültige Ausgabe von „setImmediate“ liegt also vor „timeout“. Es ist ersichtlich, dass die Ausführungsreihenfolge der beiden mit der aktuellen Ausführungsphase zusammenhängt.

【Verwandte Empfehlungen:

JavaScript-Video-Tutorial

, Web-Frontend

Das obige ist der detaillierte Inhalt vonLassen Sie uns über die Prinzipien und Beispiele der JavaScript-Ereignisschleife sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen