node.js事件循環(huán)機(jī)制及與js區(qū)別詳解
一、是什么
在瀏覽器事件循環(huán)(opens new window)中,我們了解到javascript
在瀏覽器中的事件循環(huán)機(jī)制,其是根據(jù)HTML5
定義的規(guī)范來(lái)實(shí)現(xiàn)
而在NodeJS
中,事件循環(huán)是基于libuv
實(shí)現(xiàn),libuv
是一個(gè)多平臺(tái)的專(zhuān)注于異步IO的庫(kù),如下圖最右側(cè)所示:
上圖EVENT_QUEUE
給人看起來(lái)只有一個(gè)隊(duì)列,但EventLoop
存在6個(gè)階段,每個(gè)階段都有對(duì)應(yīng)的一個(gè)先進(jìn)先出的回調(diào)隊(duì)列
二、流程
上節(jié)講到事件循環(huán)分成了六個(gè)階段,對(duì)應(yīng)如下:
- timers階段:這個(gè)階段執(zhí)行timer(setTimeout、setInterval)的回調(diào)
- 定時(shí)器檢測(cè)階段(timers):本階段執(zhí)行 timer 的回調(diào),即 setTimeout、setInterval 里面的回調(diào)函數(shù)
- I/O事件回調(diào)階段(I/O callbacks):執(zhí)行延遲到下一個(gè)循環(huán)迭代的 I/O 回調(diào),即上一輪循環(huán)中未被執(zhí)行的一些I/O回調(diào)
- 閑置階段(idle, prepare):僅系統(tǒng)內(nèi)部使用
- 輪詢(xún)階段(poll):檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)(幾乎所有情況下,除了關(guān)閉的回調(diào)函數(shù),那些由計(jì)時(shí)器和 setImmediate() 調(diào)度的之外),其余情況 node 將在適當(dāng)?shù)臅r(shí)候在此阻塞
- 檢查階段(check):setImmediate() 回調(diào)函數(shù)在這里執(zhí)行
- 關(guān)閉事件回調(diào)階段(close callback):一些關(guān)閉的回調(diào)函數(shù),如:socket.on('close', ...)
每個(gè)階段對(duì)應(yīng)一個(gè)隊(duì)列,當(dāng)事件循環(huán)進(jìn)入某個(gè)階段時(shí), 將會(huì)在該階段內(nèi)執(zhí)行回調(diào),直到隊(duì)列耗盡或者回調(diào)的最大數(shù)量已執(zhí)行, 那么將進(jìn)入下一個(gè)處理階段
除了上述6個(gè)階段,還存在process.nextTick
,其不屬于事件循環(huán)的任何一個(gè)階段,它屬于該階段與下階段之間的過(guò)渡, 即本階段執(zhí)行結(jié)束, 進(jìn)入下一個(gè)階段前, 所要執(zhí)行的回調(diào),類(lèi)似插隊(duì)
流程圖如下所示:
在Node
中,同樣存在宏任務(wù)和微任務(wù),與瀏覽器中的事件循環(huán)相似
微任務(wù)對(duì)應(yīng)有:
- next tick queue:process.nextTick
- other queue:Promise的then回調(diào)、queueMicrotask
宏任務(wù)對(duì)應(yīng)有:
- timer queue:setTimeout、setInterval
- poll queue:IO事件
- check queue:setImmediate
- close queue:close事件
其執(zhí)行順序?yàn)椋?/p>
- next tick microtask queue
- other microtask queue
- timer queue
- poll queue
- check queue
- close queue
三、題目
通過(guò)上面的學(xué)習(xí),下面開(kāi)始看看題目
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')
分析過(guò)程:
- 先找到同步任務(wù),輸出script start
- 遇到第一個(gè) setTimeout,將里面的回調(diào)函數(shù)放到 timer 隊(duì)列中
- 遇到第二個(gè) setTimeout,300ms后將里面的回調(diào)函數(shù)放到 timer 隊(duì)列中
- 遇到第一個(gè)setImmediate,將里面的回調(diào)函數(shù)放到 check 隊(duì)列中
- 遇到第一個(gè) nextTick,將其里面的回調(diào)函數(shù)放到本輪同步任務(wù)執(zhí)行完畢后執(zhí)行
- 執(zhí)行 async1函數(shù),輸出 async1 start
- 執(zhí)行 async2 函數(shù),輸出 async2,async2 后面的輸出 async1 end進(jìn)入微任務(wù),等待下一輪的事件循環(huán)
- 遇到第二個(gè),將其里面的回調(diào)函數(shù)放到本輪同步任務(wù)執(zhí)行完畢后執(zhí)行
- 遇到 new Promise,執(zhí)行里面的立即執(zhí)行函數(shù),輸出 promise1、promise2
- then里面的回調(diào)函數(shù)進(jìn)入微任務(wù)隊(duì)列
- 遇到同步任務(wù),輸出 script end
- 執(zhí)行下一輪回到函數(shù),先依次輸出 nextTick 的函數(shù),分別是 nextTick1、nextTick2
- 然后執(zhí)行微任務(wù)隊(duì)列,依次輸出 async1 end、promise3
- 執(zhí)行timer 隊(duì)列,依次輸出 setTimeout0
- 接著執(zhí)行 check 隊(duì)列,依次輸出 setImmediate
- 300ms后,timer 隊(duì)列存在任務(wù),執(zhí)行輸出 setTimeout2
執(zhí)行結(jié)果如下:
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2
最后有一道是關(guān)于setTimeout
與setImmediate
的輸出順序
setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); });
輸出情況如下:
情況一:
setTimeout
setImmediate
情況二:
setImmediate
setTimeout
分析下流程:
- 外層同步代碼一次性全部執(zhí)行完,遇到異步API就塞到對(duì)應(yīng)的階段
- 遇到
setTimeout
,雖然設(shè)置的是0毫秒觸發(fā),但實(shí)際上會(huì)被強(qiáng)制改成1ms,時(shí)間到了然后塞入times
階段 - 遇到
setImmediate
塞入check
階段 - 同步代碼執(zhí)行完畢,進(jìn)入Event Loop
- 先進(jìn)入
times
階段,檢查當(dāng)前時(shí)間過(guò)去了1毫秒沒(méi)有,如果過(guò)了1毫秒,滿(mǎn)足setTimeout
條件,執(zhí)行回調(diào),如果沒(méi)過(guò)1毫秒,跳過(guò) - 跳過(guò)空的階段,進(jìn)入check階段,執(zhí)行
setImmediate
回調(diào)
這里的關(guān)鍵在于這1ms,如果同步代碼執(zhí)行時(shí)間較長(zhǎng),進(jìn)入Event Loop
的時(shí)候1毫秒已經(jīng)過(guò)了,setTimeout
先執(zhí)行,如果1毫秒還沒(méi)到,就先執(zhí)行了setImmediate
以上就是node.js事件循環(huán)機(jī)制及與js區(qū)別詳解的詳細(xì)內(nèi)容,更多關(guān)于node.js事件循環(huán)與js區(qū)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
NodeJS學(xué)習(xí)筆記之Connect中間件模塊(二)
本文續(xù)上文的內(nèi)容,介紹下nodejs中connect中間件的使用方式及用途,希望大家喜歡。2015-01-01初識(shí)NodeJS服務(wù)端開(kāi)發(fā)入門(mén)(Express+MySQL)
本篇文章主要介紹了初識(shí)NodeJS服務(wù)端開(kāi)發(fā)入門(mén)(Express+MySQL),可以對(duì)數(shù)據(jù)庫(kù)中的一張表進(jìn)行簡(jiǎn)單的CRUD操作,有興趣的可以了解一下。2017-04-04node實(shí)現(xiàn)shell命令管理工具及commander.js學(xué)習(xí)
這篇文章主要為大家介紹了node實(shí)現(xiàn)shell命令管理工具及commander.js學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09kafka調(diào)試中遇到Connection to node -1 could not be established. Br
這篇文章主要介紹了kafka調(diào)試中遇到Connection to node -1 could not be established. Broker may not be available的解決方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-09-09nodejs如何在package.json中設(shè)置多條啟動(dòng)命令
這篇文章主要介紹了nodejs如何在package.json中設(shè)置多條啟動(dòng)命令,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03利用pm2部署多個(gè)node.js項(xiàng)目的配置教程
目前似乎最常見(jiàn)的線上部署nodejs項(xiàng)目的有forever,pm2這兩種,而下面這篇文章主要給大家介紹了關(guān)于利用pm2部署多個(gè)node.js項(xiàng)目的配置教程,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-10-10Nodejs對(duì)postgresql基本操作的封裝方法
今天小編就為大家分享一篇Nodejs對(duì)postgresql基本操作的封裝方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-02-02在Node.js中實(shí)現(xiàn)后端與前端的交互的方法詳解
在前后端不分離的應(yīng)用模式中,前端頁(yè)面看到的效果都是由后端控制,由后端渲染頁(yè)面或重定向,也就是后端需要控制前端的展示,前端與后端的耦合度很高, 所以本文給大家介紹了在Node.js中實(shí)現(xiàn)后端與前端的交互的方法,需要的朋友可以參考下2024-09-09