您好,登錄后才能下訂單哦!
這篇文章主要介紹“Node事件循環(huán)機(jī)制是什么”,在日常操作中,相信很多人在Node事件循環(huán)機(jī)制是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Node事件循環(huán)機(jī)制是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
雖然js
可以在瀏覽器中執(zhí)行又可以在node
中執(zhí)行,但是它們的事件循環(huán)機(jī)制并不是一樣的。并且有很大的區(qū)別。
在說(shuō)Node
事件循環(huán)機(jī)制之前,我們先來(lái)討論兩個(gè)問(wèn)題
學(xué)習(xí)事件循環(huán)可以讓開(kāi)發(fā)者明白JavaScript
的運(yùn)行機(jī)制是怎么樣的。
事件循環(huán)機(jī)制用于管理異步API的回調(diào)函數(shù)什么時(shí)候回到主線程中執(zhí)行。
Node.js采用的是異步IO模型。同步API在主線程中執(zhí)行,異步API在底層的C++維護(hù)的線程中執(zhí)行,異步API的回調(diào)函數(shù)也會(huì)在主線程中執(zhí)行。【相關(guān)教程推薦:nodejs視頻教程、編程教學(xué)】
在Javascript應(yīng)用運(yùn)行時(shí),眾多異步API的回調(diào)函數(shù)什么時(shí)候能回到主線程中調(diào)用呢?這就是事件環(huán)環(huán)機(jī)制做的事情,管理異步API的回調(diào)函數(shù)什么時(shí)候回到主線程中執(zhí)行。
在Node
中的事件循環(huán)分為六個(gè)階段。
在事件循環(huán)中的每個(gè)階段都有一個(gè)隊(duì)列,存儲(chǔ)要執(zhí)行的回調(diào)函數(shù),事件循環(huán)機(jī)制會(huì)按照先進(jìn)先出的方式執(zhí)行他們直到隊(duì)列為空。
這六個(gè)階段都存儲(chǔ)著異步回調(diào)函數(shù),所以還是遵循先執(zhí)行主線程同步代碼,當(dāng)同步代碼執(zhí)行完后再來(lái)輪詢(xún)這六個(gè)階段。
接下來(lái),我們來(lái)詳細(xì)看看這六個(gè)階段里面存儲(chǔ)的都是什么
Timers
:用于存儲(chǔ)定時(shí)器的回調(diào)函數(shù)(setlnterval,setTimeout)。
Pendingcallbacks
:執(zhí)行與操作系統(tǒng)相關(guān)的回調(diào)函數(shù),比如啟動(dòng)服務(wù)器端應(yīng)用時(shí)監(jiān)聽(tīng)端口操作的回調(diào)函數(shù)就在這里調(diào)用。
idle,prepare
:系統(tǒng)內(nèi)部使用。(這個(gè)我們程序員不用管)
Poll
:存儲(chǔ)1/O操作的回調(diào)函數(shù)隊(duì)列,比如文件讀寫(xiě)操作的回調(diào)函數(shù)。
在這個(gè)階段需要特別注意,如果事件隊(duì)列中有回調(diào)函數(shù),則執(zhí)行它們直到清空隊(duì)列 ,否則事件循環(huán)將在此階段停留一段時(shí)間以等待新的回調(diào)函數(shù)進(jìn)入。
但是對(duì)于這個(gè)等待并不是一定的,而是取決于以下兩個(gè)條件:
如果setlmmediate隊(duì)列(check階段)中存在要執(zhí)行的調(diào)函數(shù)。這種情況就不會(huì)等待。
timers隊(duì)列中存在要執(zhí)行的回調(diào)函數(shù),在這種情況下也不會(huì)等待。事件循環(huán)將移至check階段,然后移至Closingcallbacks階段,并最終從timers階段進(jìn)入下一次循環(huán)。
Check
:存儲(chǔ)setlmmediate的回調(diào)函數(shù)。
Closingcallbacks
:執(zhí)行與關(guān)閉事件相關(guān)的回調(diào),例如關(guān)閉數(shù)據(jù)庫(kù)連接的回調(diào)函數(shù)等。
跟瀏覽器中的js
一樣,node
中的異步代碼也分為宏任務(wù)和微任務(wù),只是它們之間的執(zhí)行順序有所區(qū)別。
我們?cè)賮?lái)看看Node
中都有哪些宏任務(wù)和微任務(wù)
setlnterval
setimeout
setlmmediate
I/O
Promise.then
Promise.catch
Promise.finally
process.nextTick
在node
中,對(duì)于微任務(wù)和宏任務(wù)的執(zhí)行順序到底是怎樣的呢?
在node
中,微任務(wù)的回調(diào)函數(shù)被放置在微任務(wù)隊(duì)列中,宏任務(wù)的回調(diào)函數(shù)被放置在宏任務(wù)隊(duì)列中。
微任務(wù)優(yōu)先級(jí)高于宏任務(wù)。當(dāng)微任務(wù)事件隊(duì)列中存在可以執(zhí)行的回調(diào)函數(shù)時(shí),事件循環(huán)在執(zhí)行完當(dāng)前階段的回調(diào)函數(shù)后會(huì)暫停進(jìn)入事件循環(huán)的下一個(gè)階段,而會(huì)立即進(jìn)入微任務(wù)的事件隊(duì)列中開(kāi)始執(zhí)行回調(diào)函數(shù),當(dāng)微任務(wù)隊(duì)列中的回調(diào)函數(shù)執(zhí)行完成后,事件循環(huán)才會(huì)進(jìn)入到下一個(gè)段開(kāi)始執(zhí)行回調(diào)函數(shù)。
對(duì)于微任務(wù)我們還有個(gè)點(diǎn)需要特別注意。那就是雖然nextTick
同屬于微任務(wù),但是它的優(yōu)先級(jí)是高于其它微任務(wù),在執(zhí)行微任務(wù)時(shí),只有nextlick
中的所有回調(diào)函數(shù)執(zhí)行完成后才會(huì)開(kāi)始執(zhí)行其它微任務(wù)。
總的來(lái)說(shuō)就是當(dāng)主線程同步代碼執(zhí)行完畢后會(huì)優(yōu)先清空微任務(wù)(如果微任務(wù)繼續(xù)產(chǎn)生微任務(wù)則會(huì)再次清空),然后再到下個(gè)事件循環(huán)階段。并且微任務(wù)的執(zhí)行是穿插在事件循環(huán)六個(gè)階段中間的,也就是每次事件循環(huán)進(jìn)入下個(gè)階段前會(huì)判斷微任務(wù)隊(duì)列是否為空,為空才會(huì)進(jìn)入下個(gè)階段,否則先清空微任務(wù)隊(duì)列。
下面我們用代碼實(shí)操來(lái)驗(yàn)證前面所說(shuō)的。
在Node
應(yīng)用程序啟動(dòng)后,并不會(huì)立即進(jìn)入事件循環(huán),而是先執(zhí)行同步代碼,從上到下開(kāi)始執(zhí)行,同步API立即執(zhí)行,異步API交給C++維護(hù)的線程執(zhí)行,異步API的回調(diào)函數(shù)被注冊(cè)到對(duì)應(yīng)的事件隊(duì)列中。當(dāng)所有同步代碼執(zhí)行完成后,才會(huì)進(jìn)入事件循環(huán)。
console.log("start");
setTimeout(() => {
console.log("setTimeout 1");
});
setTimeout(() => {
console.log("setTimeout 2");
});
console.log("end");
我們來(lái)看執(zhí)行結(jié)果
可以看到,先執(zhí)行同步代碼,然后才會(huì)進(jìn)入事件循環(huán)執(zhí)行異步代碼,在timers
階段執(zhí)行兩個(gè)setTimeout
回調(diào)。
我們知道setTimeout
是在timers
階段執(zhí)行,setImmediate
是在check
階段執(zhí)行。并且事件循環(huán)是從timers
階段開(kāi)始的。所以會(huì)先執(zhí)行setTimeout
再執(zhí)行setImmediate
。
對(duì)于上面的分析一定對(duì)嗎?
我們來(lái)看例子
console.log("start");
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
const sleep = (delay) => {
const startTime = +new Date();
while (+new Date() - startTime < delay) {
continue;
}
};
sleep(2000);
console.log("end");
執(zhí)行上面的代碼,輸出如下
先執(zhí)行setTimeout
再執(zhí)行setImmediate
接下來(lái)我們來(lái)改造下上面的代碼,把延遲器去掉,看看會(huì)輸出什么
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
我們運(yùn)行了七次,可以看到其中有兩次是先運(yùn)行的setImmediate
怎么回事呢?不是先timers
階段再到check
階段嗎?怎么會(huì)變呢?
其實(shí)這就得看進(jìn)入事件循環(huán)的時(shí)候,異步回調(diào)有沒(méi)有完全準(zhǔn)備好了。對(duì)于最開(kāi)始的例子,因?yàn)橛?000毫秒的延遲,所以進(jìn)入事件循環(huán)的時(shí)候,setTimeout
回調(diào)是一定準(zhǔn)備好了的。所以執(zhí)行順序不會(huì)變。但是對(duì)于這個(gè)例子,因?yàn)橹骶€程沒(méi)有同步代碼需要執(zhí)行,所以一開(kāi)始就進(jìn)入事件循環(huán),但是在進(jìn)入事件循環(huán)的時(shí)候,setTimeout
的回調(diào)并不是一定完全準(zhǔn)備好的,所以就會(huì)有先到check
階段執(zhí)行setImmediate
回調(diào)函數(shù),再到下一次事件循環(huán)的timers
階段來(lái)執(zhí)行setTimeout
的回調(diào)。
那在什么情況下同樣的延遲時(shí)間,setImmediate
回調(diào)函數(shù)一定會(huì)優(yōu)先于setTimeout
的回調(diào)呢?
其實(shí)很簡(jiǎn)單,只要將這兩者放到timers
階段和check
階段之間的Pendingcallbacks、idle,prepare、poll
階段中任意一個(gè)階段就可以了。因?yàn)檫@些階段完執(zhí)行完是一定會(huì)先到check
再到timers
階段的。
我們以poll
階段為例,將這兩者寫(xiě)在IO操作中。
const fs = require("fs");
fs.readFile("./fstest.js", "utf8", (err, data) => {
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
});
我們也來(lái)執(zhí)行七次,可以看到,每次都是setImmediate
先執(zhí)行。
所以總的來(lái)說(shuō),同樣的延遲時(shí)間,setTimeout
并不是百分百先于setImmediate
執(zhí)行。
主線程同步代碼執(zhí)行完畢后,會(huì)先執(zhí)行微任務(wù)再執(zhí)行宏任務(wù)。
我們來(lái)看下面的例子
console.log("start");
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
Promise.resolve().then(() => {
console.log("Promise.resolve");
});
console.log("end");
我們運(yùn)行一下看結(jié)果,可以看到它是先執(zhí)行了微任務(wù)然后再執(zhí)行宏任務(wù)
在微任務(wù)中nextTick
的優(yōu)先級(jí)是最高的。
我們來(lái)看下面的例子
console.log("start");
setTimeout(() => {
console.log("setTimeout");
});
setImmediate(() => {
console.log("setImmediate");
});
Promise.resolve().then(() => {
console.log("Promise.resolve");
});
process.nextTick(() => {
console.log("process.nextTick");
});
console.log("end");
我們運(yùn)行上面的代碼,可以看到就算nextTick
定義在resolve
后面,它也是先執(zhí)行的。
怎么理解這個(gè)穿插呢?其實(shí)就是在事件循環(huán)的六個(gè)階段每個(gè)階段執(zhí)行完后會(huì)清空微任務(wù)隊(duì)列。
我們來(lái)看例子,我們建立了timers、check、poll
三個(gè)階段,并且每個(gè)階段都產(chǎn)生了微任務(wù)。
// timers階段
setTimeout(() => {
console.log("setTimeout");
Promise.resolve().then(() => {
console.log("setTimeout Promise.resolve");
});
});
// check階段
setImmediate(() => {
console.log("setImmediate");
Promise.resolve().then(() => {
console.log("setImmediate Promise.resolve");
});
});
// 微任務(wù)
Promise.resolve().then(() => {
console.log("Promise.resolve");
});
// 微任務(wù)
process.nextTick(() => {
console.log("process.nextTick");
Promise.resolve().then(() => {
console.log("nextTick Promise.resolve");
});
});
我們來(lái)執(zhí)行上面的代碼
可以看到,先執(zhí)行微任務(wù),再執(zhí)行宏任務(wù)。先process.nextTick -> Promise.resolve
。并且如果微任務(wù)繼續(xù)產(chǎn)生微任務(wù)則會(huì)再次清空,所以就又輸出了nextTick Promise.resolve
。
接下來(lái)到timer
階段,輸出setTimeout
,并且產(chǎn)生了一個(gè)微任務(wù),再進(jìn)入到下個(gè)階段前需要清空微任務(wù)隊(duì)列,所以繼續(xù)輸出setTimeout Promise.resolve
。
接下來(lái)到check
階段,輸出setImmediate
,并且產(chǎn)生了一個(gè)微任務(wù),再進(jìn)入到下個(gè)階段前需要清空微任務(wù)隊(duì)列,所以繼續(xù)輸出setImmediate Promise.resolve
。
這也就印證了微任務(wù)會(huì)穿插在各個(gè)階段之間運(yùn)行。
到此,關(guān)于“Node事件循環(huán)機(jī)制是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。