新聞中心
本文轉載自微信公眾號「JS每日一題」,作者灰灰。轉載本文請聯系JS每日一題公眾號。

創(chuàng)新互聯建站-專業(yè)網站定制、快速模板網站建設、高性價比薩嘎網站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式薩嘎網站制作公司更省心,省錢,快速模板網站建設找我們,業(yè)務覆蓋薩嘎地區(qū)。費用合理售后完善,10余年實體公司更值得信賴。
一、是什么
在瀏覽器事件循環(huán)中,我們了解到javascript在瀏覽器中的事件循環(huán)機制,其是根據HTML5定義的規(guī)范來實現
而在NodeJS中,事件循環(huán)是基于libuv實現,libuv是一個多平臺的專注于異步IO的庫,如下圖最右側所示:
上圖EVENT_QUEUE 給人看起來只有一個隊列,但EventLoop存在6個階段,每個階段都有對應的一個先進先出的回調隊列
二、流程
上節(jié)講到事件循環(huán)分成了六個階段,對應如下:
- timers階段:這個階段執(zhí)行timer(setTimeout、setInterval)的回調
- 定時器檢測階段(timers):本階段執(zhí)行 timer 的回調,即 setTimeout、setInterval 里面的回調函數
- I/O事件回調階段(I/O callbacks):執(zhí)行延遲到下一個循環(huán)迭代的 I/O 回調,即上一輪循環(huán)中未被執(zhí)行的一些I/O回調
- 閑置階段(idle, prepare):僅系統內部使用
- 輪詢階段(poll):檢索新的 I/O 事件;執(zhí)行與 I/O 相關的回調(幾乎所有情況下,除了關閉的回調函數,那些由計時器和 setImmediate() 調度的之外),其余情況 node 將在適當的時候在此阻塞
- 檢查階段(check):setImmediate() 回調函數在這里執(zhí)行
- 關閉事件回調階段(close callback):一些關閉的回調函數,如:socket.on('close', ...)
每個階段對應一個隊列,當事件循環(huán)進入某個階段時, 將會在該階段內執(zhí)行回調,直到隊列耗盡或者回調的最大數量已執(zhí)行, 那么將進入下一個處理階段
除了上述6個階段,還存在process.nextTick,其不屬于事件循環(huán)的任何一個階段,它屬于該階段與下階段之間的過渡, 即本階段執(zhí)行結束, 進入下一個階段前, 所要執(zhí)行的回調,類似插隊
流程圖如下所示:
在Node中,同樣存在宏任務和微任務,與瀏覽器中的事件循環(huán)相似
微任務對應有:
- next tick queue:process.nextTick
- other queue:Promise的then回調、queueMicrotask
宏任務對應有:
- timer queue:setTimeout、setInterval
- poll queue:IO事件
- check queue:setImmediate
- close queue:close事件
其執(zhí)行順序為:
- next tick microtask queue
- other microtask queue
- timer queue
- poll queue
- check queue
- close queue
三、題目
通過上面的學習,下面開始看看題目
- async function async1() {
- console.log('async1 start')
- await async2()
- console.log('async1 end')
- }
- async function async2() {
- console.log('async2')
- }
- console.log('script start')
- setTimeout(function () {
- console.log('setTimeout0')
- }, 0)
- setTimeout(function () {
- console.log('setTimeout2')
- }, 300)
- setImmediate(() => console.log('setImmediate'));
- process.nextTick(() => console.log('nextTick1'));
- async1();
- process.nextTick(() => console.log('nextTick2'));
- new Promise(function (resolve) {
- console.log('promise1')
- resolve();
- console.log('promise2')
- }).then(function () {
- console.log('promise3')
- })
- console.log('script end')
分析過程:
- 先找到同步任務,輸出script start
- 遇到第一個 setTimeout,將里面的回調函數放到 timer 隊列中
- 遇到第二個 setTimeout,300ms后將里面的回調函數放到 timer 隊列中
- 遇到第一個setImmediate,將里面的回調函數放到 check 隊列中
- 遇到第一個 nextTick,將其里面的回調函數放到本輪同步任務執(zhí)行完畢后執(zhí)行
- 執(zhí)行 async1函數,輸出 async1 start
- 執(zhí)行 async2 函數,輸出 async2,async2 后面的輸出 async1 end進入微任務,等待下一輪的事件循環(huán)
- 遇到第二個,將其里面的回調函數放到本輪同步任務執(zhí)行完畢后執(zhí)行
- 遇到 new Promise,執(zhí)行里面的立即執(zhí)行函數,輸出 promise1、promise2
- then里面的回調函數進入微任務隊列
- 遇到同步任務,輸出 script end
- 執(zhí)行下一輪回到函數,先依次輸出 nextTick 的函數,分別是 nextTick1、nextTick2
- 然后執(zhí)行微任務隊列,依次輸出 async1 end、promise3
- 執(zhí)行timer 隊列,依次輸出 setTimeout0
- 接著執(zhí)行 check 隊列,依次輸出 setImmediate
- 300ms后,timer 隊列存在任務,執(zhí)行輸出 setTimeout2
執(zhí)行結果如下:
- script start
- async1 start
- async2
- promise1
- promise2
- script end
- nextTick1
- nextTick2
- async1 end
- promise3
- setTimeout0
- setImmediate
- setTimeout2
最后有一道是關于setTimeout與setImmediate的輸出順序
- setTimeout(() => {
- console.log("setTimeout");
- }, 0);
- setImmediate(() => {
- console.log("setImmediate");
- });
輸出情況如下:
- 情況一:
- setTimeout
- setImmediate
- 情況二:
- setImmediate
- setTimeout
分析下流程:
- 外層同步代碼一次性全部執(zhí)行完,遇到異步API就塞到對應的階段
- 遇到setTimeout,雖然設置的是0毫秒觸發(fā),但實際上會被強制改成1ms,時間到了然后塞入times階段
- 遇到setImmediate塞入check階段
- 同步代碼執(zhí)行完畢,進入Event Loop
- 先進入times階段,檢查當前時間過去了1毫秒沒有,如果過了1毫秒,滿足setTimeout條件,執(zhí)行回調,如果沒過1毫秒,跳過
- 跳過空的階段,進入check階段,執(zhí)行setImmediate回調
- 這里的關鍵在于這1ms,如果同步代碼執(zhí)行時間較長,進入Event Loop的時候1毫秒已經過了,setTimeout先執(zhí)行,如果1毫秒還沒到,就先執(zhí)行了setImmediate
參考文獻
https://segmentfault.com/a/1190000012258592
https://juejin.cn/post/6844904100195205133
https://vue3js.cn/interview/
分享題目:面試官:說說對Node.js中的事件循環(huán)機制理解?
分享網址:http://m.5511xx.com/article/cdpdisd.html


咨詢
建站咨詢
