小編給大家分享一下JavaScript在nodejs環(huán)境下執(zhí)行機(jī)制和事件循環(huán)的示例,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司提供網(wǎng)站設(shè)計(jì)和自適應(yīng)建站服務(wù)。團(tuán)隊(duì)由有經(jīng)驗(yàn)的網(wǎng)頁設(shè)計(jì)師、程序員和市場專家組成,能夠提供從H5技術(shù),網(wǎng)站制作,廣告投放平臺(tái),模板建站到小程序設(shè)計(jì)等全方位服務(wù)。 以客戶為中心,致力于為客戶提供創(chuàng)新、高效的解決方案,幫助您打造成功的企業(yè)網(wǎng)站。1、說明
nodejs是單線程執(zhí)行的,同時(shí)它又是基于事件驅(qū)動(dòng)的非阻塞IO編程模型。這就使得我們不用等待異步操作結(jié)果返回,就可以繼續(xù)往下執(zhí)行代碼。當(dāng)異步事件觸發(fā)之后,就會(huì)通知主線程,主線程執(zhí)行相應(yīng)事件的回調(diào)。
本篇文章講解node中JavaScript的代碼的執(zhí)行流程,下面是測試代碼,如果你知道輸出的結(jié)果,那么就不需要再看本篇文章,如果不知道輸出結(jié)果,那么本片文章可幫助你了解:
console.log(1) setTimeout(function () { new Promise(function (resolve) { console.log(2) resolve() }) .then(() => { console.log(3) }) }) setTimeout(function () { console.log(4) })
復(fù)雜的:
setTimeout(() => { console.log('1') new Promise((resolve) => { console.log('2'); resolve(); }) .then(() => { console.log('3') }) new Promise((resolve)=> { console.log('4'); resolve()}) .then(() => { console.log('5') }) setTimeout(() => { console.log('6') setTimeout(() => { console.log('7') new Promise((resolve) => { console.log('8'); resolve() }) .then( () => { console.log('9') }) new Promise((resolve) => { console.log('10'); resolve() }) .then(() => { console.log('11') }) }) setTimeout(() => { console.log('12') }, 0) }) setTimeout(() => { console.log('13') }, 0) }) setTimeout(() => { console.log('14') }, 0) new Promise((resolve) => { console.log('15'); resolve() }) .then( ()=> { console.log('16') }) new Promise((resolve) => { console.log('17'); resolve() }) .then(() => { console.log('18') })
2. nodejs的啟動(dòng)過程
node.js啟動(dòng)過程可以分為以下步驟:
3. nodejs的事件循環(huán)詳解
Nodejs 將消息循環(huán)又細(xì)分為 6 個(gè)階段(官方叫做 Phase), 每個(gè)階段都會(huì)有一個(gè)類似于隊(duì)列的結(jié)構(gòu), 存儲(chǔ)著該階段需要處理的回調(diào)函數(shù).
Nodejs 為了防止某個(gè) 階段 任務(wù)太多, 導(dǎo)致后續(xù)的 階段 發(fā)生饑餓的現(xiàn)象, 所以消息循環(huán)的每一個(gè)迭代(iterate) 中, 每個(gè) 階段 執(zhí)行回調(diào)都有個(gè)大數(shù)量. 如果超過數(shù)量的話也會(huì)強(qiáng)行結(jié)束當(dāng)前 階段而進(jìn)入下一個(gè) 階段. 這一條規(guī)則適用于消息循環(huán)中的每一個(gè) 階段.
這是消息循環(huán)的第一個(gè)階段, 用一個(gè) for
循環(huán)處理所有 setTimeout
和 setInterval
的回調(diào).
這些回調(diào)被保存在一個(gè)最小堆(min heap) 中. 這樣引擎只需要每次判斷頭元素, 如果符合條件就拿出來執(zhí)行, 直到遇到一個(gè)不符合條件或者隊(duì)列空了, 才結(jié)束 Timer Phase.
Timer 階段中判斷某個(gè)回調(diào)是否符合條件的方法也很簡單. 消息循環(huán)每次進(jìn)入 Timer 的時(shí)候都會(huì)保存一下當(dāng)時(shí)的系統(tǒng)時(shí)間,然后只要看上述最小堆中的回調(diào)函數(shù)設(shè)置的啟動(dòng)時(shí)間是否超過進(jìn)入 Timer 時(shí)保存的時(shí)間, 如果超過就拿出來執(zhí)行.
執(zhí)行除了close callbacks
、setTimeout()
、setInterval()
、setImmediate()
回調(diào)之外幾乎所有回調(diào),比如說TCP連接發(fā)生錯(cuò)誤
、 fs.read
, socket
等 IO 操作的回調(diào)函數(shù), 同時(shí)也包括各種 error 的回調(diào).
系統(tǒng)內(nèi)部的一些調(diào)用。
這是整個(gè)消息循環(huán)中最重要的一個(gè) 階段, 作用是等待異步請求和數(shù)據(jù),因?yàn)樗瘟苏麄€(gè)消息循環(huán)機(jī)制.
poll階段有兩個(gè)主要的功能:一是執(zhí)行下限時(shí)間已經(jīng)達(dá)到的timers的回調(diào),一是處理poll隊(duì)列里的事件。
注:Node的很多API都是基于事件訂閱完成的,比如fs.readFile,這些回調(diào)應(yīng)該都在poll
階段完成。
當(dāng)事件循環(huán)進(jìn)入poll階段:
poll
隊(duì)列不為空的時(shí)候,事件循環(huán)肯定是先遍歷隊(duì)列并同步執(zhí)行回調(diào),直到隊(duì)列清空或執(zhí)行回調(diào)數(shù)達(dá)到系統(tǒng)上限。poll
隊(duì)列為空的時(shí)候,這里有兩種情況。
setImmediate()
設(shè)定了回調(diào),那么事件循環(huán)直接結(jié)束poll
階段進(jìn)入check
階段來執(zhí)行check
隊(duì)列里的回調(diào)。如果代碼沒有被設(shè)定setImmediate()
設(shè)定回調(diào):
Poll階段,當(dāng)js層代碼注冊的事件回調(diào)都沒有返回的時(shí)候,事件循環(huán)會(huì)暫時(shí)阻塞在poll階段,解除阻塞的條件:
- 在poll階段執(zhí)行的時(shí)候,會(huì)傳入一個(gè)timeout超時(shí)時(shí)間,該超時(shí)時(shí)間就是poll階段的大阻塞時(shí)間。
- timeout時(shí)間未到的時(shí)候,如果有事件返回,就執(zhí)行該事件注冊的回調(diào)函數(shù)。timeout超時(shí)時(shí)間到了,則退出poll階段,執(zhí)行下一個(gè)階段。
這個(gè) timeout 設(shè)置為多少合適呢? 答案就是 Timer Phase 中最近要執(zhí)行的回調(diào)啟動(dòng)時(shí)間到現(xiàn)在的差值, 假設(shè)這個(gè)差值是 detal. 因?yàn)?Poll Phase 后面沒有等待執(zhí)行的回調(diào)了. 所以這里最多等待 delta 時(shí)長, 如果期間有事件喚醒了消息循環(huán), 那么就繼續(xù)下一個(gè) Phase 的工作; 如果期間什么都沒發(fā)生, 那么到了 timeout 后, 消息循環(huán)依然要進(jìn)入后面的 Phase, 讓下一個(gè)迭代的 Timer Phase 也能夠得到執(zhí)行.
Nodejs 就是通過 Poll Phase, 對(duì) IO 事件的等待和內(nèi)核異步事件的到達(dá)來驅(qū)動(dòng)整個(gè)消息循環(huán)的.
這個(gè)階段只處理 setImmediate 的回調(diào)函數(shù).
那么為什么這里要有專門一個(gè)處理 setImmediate 的 階段 呢? 簡單來說, 是因?yàn)?Poll 階段可能設(shè)置一些回調(diào), 希望在 Poll 階段 后運(yùn)行. 所以在 Poll 階段 后面增加了這個(gè) Check 階段.
專門處理一些 close 類型的回調(diào). 比如 socket.on('close', ...)
. 用于資源清理.
1、node初始化
初始化node環(huán)境
執(zhí)行輸入的代碼
執(zhí)行process.nextTick
回調(diào)
執(zhí)行微任務(wù)(microtasks)
2、進(jìn)入事件循環(huán)
2.1、進(jìn)入Timer
階段
Timer
隊(duì)列是否有到期的Timer
的回調(diào),如果有,將到期的所有Timer
回調(diào)按照TimerId
升序執(zhí)行process.nextTick
任務(wù),如果有,全部執(zhí)行2.2、進(jìn)入Pending I/O Callback
階段
Pending I/O Callback
的回調(diào),如果有,執(zhí)行回調(diào)。如果沒有退出該階段process.nextTick
任務(wù),如果有,全部執(zhí)行2.3、進(jìn)入idle,prepare
階段
這個(gè)階段與JavaScript關(guān)系不大,略過
2.4、進(jìn)入Poll
階段
首先檢查是否存在尚未完成的回調(diào),如果存在,分如下兩種情況:
第一種情況:有可執(zhí)行的回調(diào)
執(zhí)行所有可用回調(diào)(包含到期的定時(shí)器還有一些IO事件等)
檢查是否有process.nextTick
任務(wù),如果有,全部執(zhí)行
檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行
退出該階段
第二種情況:沒有可執(zhí)行的回調(diào)
檢查是否有immediate
回調(diào),如果有,退出Poll階段。如果沒有,阻塞在此階段,等待新的事件通知
如果不存在尚未完成的回調(diào),退出Poll階段
2.5、進(jìn)入check
階段
如果有immediate回調(diào),則執(zhí)行所有immediate回調(diào)
檢查是否有process.nextTick
任務(wù),如果有,全部執(zhí)行
檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行
退出該階段
2.6、進(jìn)入closing
階段
如果有immediate回調(diào),則執(zhí)行所有immediate回調(diào)
檢查是否有process.nextTick
任務(wù),如果有,全部執(zhí)行
檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行
退出該階段
3、檢查是否有活躍的handles(定時(shí)器、IO等事件句柄)
如果有,繼續(xù)下一輪事件循環(huán)
如果沒有,結(jié)束事件循環(huán),退出程序
注意:
事件循環(huán)的每一個(gè)子階段退出之前都會(huì)按順序執(zhí)行如下過程:
檢查是否有 process.nextTick 回調(diào),如果有,全部執(zhí)行。
檢查是否有 微任務(wù)(promise),如果有,全部執(zhí)行。
事件循環(huán)隊(duì)列先保證所有的process.nextTick
回調(diào),然后將所有的Promise
回調(diào)追加在后面,最終在每個(gè)階段結(jié)束的時(shí)候一次性拿出來執(zhí)行。
此外,process.nextTick
和Promise
回調(diào)的數(shù)量是受限制的,也就是說,如果一直往這個(gè)隊(duì)列中加入回調(diào),那么整個(gè)事件循環(huán)就會(huì)被卡住
。
這兩個(gè)方法的回調(diào)到底誰快?
如下面的例子:
setImmediate(() => console.log(2)) setTimeout(() => console.log(1))
使用nodejs多次執(zhí)行后,發(fā)現(xiàn)輸出結(jié)果有時(shí)是1 2
,有時(shí)是2 1
。
對(duì)于多次執(zhí)行輸出結(jié)果不同,需要了解事件循環(huán)的基礎(chǔ)問題。
首先,Nodejs啟動(dòng),初始化環(huán)境后加載我們的JS代碼(index.js).發(fā)生了兩件事(此時(shí)尚未進(jìn)入消息循環(huán)環(huán)節(jié)):
setImmediate 向 Check 階段 中添加了回調(diào) console.log(2);setTimeout 向 Timer 階段 中添加了回調(diào) console.log(1)
這時(shí)候, 要初始化階段完畢, 要進(jìn)入 Nodejs 消息循環(huán)了。
為什么會(huì)有兩種輸出呢? 接下來一步很關(guān)鍵:
當(dāng)執(zhí)行到 Timer 階段 時(shí), 會(huì)發(fā)生兩種可能. 因?yàn)槊恳惠喌鷦倓傔M(jìn)入 Timer 階段 時(shí)會(huì)取系統(tǒng)時(shí)間保存起來, 以 ms(毫秒) 為最小單位.
如果 Timer 階段 中回調(diào)預(yù)設(shè)的時(shí)間 > 消息循環(huán)所保存的時(shí)間, 則執(zhí)行 Timer 階段 中的該回調(diào). 這種情況下先輸出 1, 直到 Check 階段 執(zhí)行后,輸出2.總的來說, 結(jié)果是 1 2.
如果運(yùn)行比較快, Timer 階段 中回調(diào)預(yù)設(shè)的時(shí)間可能剛好等于消息循環(huán)所保存的時(shí)間, 這種情況下, Timer 階段 中的回調(diào)得不到執(zhí)行, 則繼續(xù)下一個(gè) 階段. 直到 Check 階段, 輸出 2. 然后等下一輪迭代的 Timer 階段, 這時(shí)的時(shí)間一定是滿足 Timer 階段 中回調(diào)預(yù)設(shè)的時(shí)間 > 消息循環(huán)所保存的時(shí)間 , 所以 console.log(1) 得到執(zhí)行, 輸出 1. 總的來說, 結(jié)果就是 2 1.
所以, 輸出不穩(wěn)定的原因就取決于進(jìn)入 Timer 階段 的時(shí)間是否和執(zhí)行 setTimeout 的時(shí)間在 1ms 內(nèi). 如果把代碼改成如下, 則一定會(huì)得到穩(wěn)定的輸出:
require('fs').readFile('my-file-path.txt', () => { setImmediate(() => console.log(2)) setTimeout(() => console.log(1)) });
這是因?yàn)橄⒀h(huán)在 Pneding I/O Phase
才向 Timer 和 Check 隊(duì)列插入回調(diào). 這時(shí)按照消息循環(huán)的執(zhí)行順序, Check 一定在 Timer 之前執(zhí)行。
從性能角度講, setTimeout 的處理是在 Timer Phase, 其中 min heap 保存了 timer 的回調(diào), 因此每執(zhí)行一個(gè)回調(diào)的同時(shí)都會(huì)涉及到堆調(diào)整. 而 setImmediate 僅僅是清空一個(gè)隊(duì)列. 效率自然會(huì)高很多.
再從執(zhí)行時(shí)機(jī)上講. setTimeout(..., 0) 和 setImmediate 完全屬于兩個(gè)階段.
下面以一段代碼來說明nodejs運(yùn)行JavaScript的機(jī)制。
如下面一段代碼:
setTimeout(() => { // settimeout1 console.log('1') new Promise((resolve) => { console.log('2'); resolve(); }) // Promise3 .then(() => { console.log('3') }) new Promise((resolve)=> { console.log('4'); resolve()}) // Promise4 .then(() => { console.log('5') }) setTimeout(() => { // settimeout3 console.log('6') setTimeout(() => { // settimeout5 console.log('7') new Promise((resolve) => { console.log('8'); resolve() }) // Promise5 .then( () => { console.log('9') }) new Promise((resolve) => { console.log('10'); resolve() }) // Promise6 .then(() => { console.log('11') }) }) setTimeout(() => { console.log('12') }, 0) // settimeout6 }) setTimeout(() => { console.log('13') }, 0) // settimeout4 }) setTimeout(() => { console.log('14') }, 0) // settimeout2 new Promise((resolve) => { console.log('15'); resolve() }) // Promise1 .then( ()=> { console.log('16') }) new Promise((resolve) => { console.log('17'); resolve() }) // Promise2 .then(() => { console.log('18') })
上面代碼執(zhí)行過程:
node初始化
執(zhí)行JavaScript代碼
遇到setTimeout
, 把回調(diào)函數(shù)放到Timer
隊(duì)列中,記為settimeout1
遇到setTimeout
, 把回調(diào)函數(shù)放到Timer
隊(duì)列中,記為settimeout2
遇到Promise
,執(zhí)行,輸出15,把回調(diào)函數(shù)放到微任務(wù)
隊(duì)列,記為Promise1
遇到Promise
,執(zhí)行,輸出17,把回調(diào)函數(shù)放到微任務(wù)
隊(duì)列,記為Promise2
代碼執(zhí)行結(jié)束,此階段輸出結(jié)果:15 17
沒有process.nextTick
回調(diào),略過
執(zhí)行微任務(wù)
檢查微任務(wù)隊(duì)列是否有可執(zhí)行回調(diào),此時(shí)隊(duì)列有2個(gè)回調(diào):Promise1、Promise2
執(zhí)行Promise1回調(diào),輸出16
執(zhí)行Promise2回調(diào),輸出18
此階段輸出結(jié)果:16 18
進(jìn)入第一次事件循環(huán)
進(jìn)入Timer階段
檢查Timer隊(duì)列是否有可執(zhí)行的回調(diào),此時(shí)隊(duì)列有2個(gè)回調(diào):settimeout1、settimeout2
執(zhí)行settimeout1回調(diào):
輸出1、2、4
添加了2個(gè)微任務(wù),記為Promise3、Promise4
添加了2個(gè)Timer任務(wù),記為settimeout3、settimeout4
執(zhí)行settimeout2回調(diào),輸出14
Timer隊(duì)列任務(wù)執(zhí)行完畢
沒有process.nextTick
回調(diào),略過
檢查微任務(wù)隊(duì)列是否有可執(zhí)行回調(diào),此時(shí)隊(duì)列有2個(gè)回調(diào):Promise3、Promise4
按順序執(zhí)行2個(gè)微任務(wù),輸出3、5
此階段輸出結(jié)果:1 2 4 14 3 5
Pending I/O Callback階段沒有任務(wù),略過
進(jìn)入 Poll 階段
檢查是否存在尚未完成的回調(diào),此時(shí)有2個(gè)回調(diào):settimeout3、settimeout4
執(zhí)行settimeout3回調(diào)
輸出6
添加了2個(gè)Timer任務(wù),記為settimeout5、settimeout6
執(zhí)行settimeout4回調(diào),輸出13
沒有process.nextTick
回調(diào),略過
沒有微任務(wù),略過
此階段輸出結(jié)果:6 13
check、closing階段沒有任務(wù),略過
檢查是否還有活躍的handles(定時(shí)器、IO等事件句柄)
,有,繼續(xù)下一輪事件循環(huán)
進(jìn)入第二次事件循環(huán)
進(jìn)入Timer階段
檢查Timer隊(duì)列是否有可執(zhí)行的回調(diào),此時(shí)隊(duì)列有2個(gè)回調(diào):settimeout5、settimeout6
執(zhí)行settimeout5回調(diào):
輸出7、 8、10
添加了2個(gè)微任務(wù),記為Promise5、Promise6
執(zhí)行settimeout6回調(diào),輸出12
沒有process.nextTick
回調(diào),略過
檢查微任務(wù)隊(duì)列是否有可執(zhí)行回調(diào),此時(shí)隊(duì)列有2個(gè)回調(diào):Promise5、Promise6
按順序執(zhí)行2個(gè)微任務(wù),輸出9、11
此階段輸出結(jié)果:7 8 10 12 9 11
Pending I/O Callback、Poll、check、closing階段沒有任務(wù),略過
檢查是否還有活躍的handles(定時(shí)器、IO等事件句柄)
,沒有了,結(jié)束事件循環(huán),退出程序
程序執(zhí)行結(jié)束,輸出結(jié)果:15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11
看完了這篇文章,相信你對(duì)JavaScript在nodejs環(huán)境下執(zhí)行機(jī)制和事件循環(huán)的示例有了一定的了解,想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
當(dāng)前文章:JavaScript在nodejs環(huán)境下執(zhí)行機(jī)制和事件循環(huán)的示例-創(chuàng)新互聯(lián)
鏈接分享:http://www.rwnh.cn/article44/epghe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、關(guān)鍵詞優(yōu)化、網(wǎng)站內(nèi)鏈、App開發(fā)、軟件開發(fā)、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容