JavaScript事件循環(huán)的原理是什么

今天小編給大家分享一下JavaScript事件循環(huán)的原理是什么的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

成都創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括云龍網(wǎng)站建設(shè)、云龍網(wǎng)站制作、云龍網(wǎng)頁(yè)制作以及云龍網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,云龍網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到云龍省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

理解 JavaScript 的事件循環(huán)往往伴隨著宏任務(wù)和微任務(wù)、JavaScript 單線程執(zhí)行過(guò)程及瀏覽器異步機(jī)制等相關(guān)問(wèn)題,而瀏覽器和  NodeJS  中的事件循環(huán)實(shí)現(xiàn)也是有很大差別。熟悉事件循環(huán),了解瀏覽器運(yùn)行機(jī)制將對(duì)我們理解 JavaScript 的執(zhí)行過(guò)程,以及在排查代碼運(yùn)行問(wèn)題時(shí)有很大幫助。

瀏覽器 JS 異步執(zhí)行的原理

JS 是單線程的,也就是同一個(gè)時(shí)刻只能做一件事情,那么思考:為什么瀏覽器可以同時(shí)執(zhí)行異步任務(wù)呢?

因?yàn)闉g覽器是多線程的,當(dāng) JS 需要執(zhí)行異步任務(wù)時(shí),瀏覽器會(huì)另外啟動(dòng)一個(gè)線程去執(zhí)行該任務(wù)。也就是說(shuō),“JS 是單線程的”指的是執(zhí)行 JS 代碼的線程只有一個(gè),是瀏覽器提供的 JS 引擎線程(主線程)。瀏覽器中還有定時(shí)器線程和 HTTP 請(qǐng)求線程等,這些線程主要不是來(lái)跑 JS 代碼的。

比如主線程中需要發(fā)一個(gè) AJAX 請(qǐng)求,就把這個(gè)任務(wù)交給另一個(gè)瀏覽器線程(HTTP 請(qǐng)求線程)去真正發(fā)送請(qǐng)求,待請(qǐng)求回來(lái)了,再將 callback 里需要執(zhí)行的 JS 回調(diào)交給 JS 引擎線程去執(zhí)行。**即瀏覽器才是真正執(zhí)行發(fā)送請(qǐng)求這個(gè)任務(wù)的角色,而 JS 只是負(fù)責(zé)執(zhí)行最后的回調(diào)處理。**所以這里的異步不是 JS 自身實(shí)現(xiàn)的,其實(shí)是瀏覽器為其提供的能力。

JavaScript事件循環(huán)的原理是什么

以 Chrome 為例,瀏覽器不僅有多個(gè)線程,還有多個(gè)進(jìn)程,如渲染進(jìn)程、GPU 進(jìn)程和插件進(jìn)程等。而每個(gè) tab 標(biāo)簽頁(yè)都是一個(gè)獨(dú)立的渲染進(jìn)程,所以一個(gè) tab 異常崩潰后,其他 tab 基本不會(huì)被影響。作為前端開發(fā)者,主要重點(diǎn)關(guān)注其渲染進(jìn)程,渲染進(jìn)程下包含了 JS 引擎線程、HTTP 請(qǐng)求線程和定時(shí)器線程等,這些線程為 JS 在瀏覽器中完成異步任務(wù)提供了基礎(chǔ)。

JavaScript事件循環(huán)的原理是什么

事件驅(qū)動(dòng)淺析

瀏覽器異步任務(wù)的執(zhí)行原理背后其實(shí)是一套事件驅(qū)動(dòng)的機(jī)制。事件觸發(fā)、任務(wù)選擇和任務(wù)執(zhí)行都是由事件驅(qū)動(dòng)機(jī)制來(lái)完成的。NodeJS 和瀏覽器的設(shè)計(jì)都是基于事件驅(qū)動(dòng)的,簡(jiǎn)而言之就是由特定的事件來(lái)觸發(fā)特定的任務(wù),這里的事件可以是用戶的操作觸發(fā)的,如 click 事件;也可以是程序自動(dòng)觸發(fā)的,比如瀏覽器中定時(shí)器線程在計(jì)時(shí)結(jié)束后會(huì)觸發(fā)定時(shí)器事件。而本文的主題內(nèi)容事件循環(huán)其實(shí)就是在事件驅(qū)動(dòng)模式中來(lái)管理和執(zhí)行事件的一套流程。

以一個(gè)簡(jiǎn)單場(chǎng)景為例,假設(shè)游戲界面上有一個(gè)移動(dòng)按鈕和人物模型,每次點(diǎn)擊右移后,人物模型的位置需要重新渲染,右移 1 像素。根據(jù)渲染時(shí)機(jī)的不同我們可以用不同的方式來(lái)實(shí)現(xiàn)。

JavaScript事件循環(huán)的原理是什么

實(shí)現(xiàn)方式一:事件驅(qū)動(dòng)。點(diǎn)擊按鈕后,修改坐標(biāo) positionX 時(shí),立即觸發(fā)界面渲染的事件,觸發(fā)重新渲染。

實(shí)現(xiàn)方式二:狀態(tài)驅(qū)動(dòng)或數(shù)據(jù)驅(qū)動(dòng)。點(diǎn)擊按鈕后,只修改坐標(biāo) positionX,不觸發(fā)界面渲染。在此之前會(huì)啟動(dòng)一個(gè)定時(shí)器 setInterval,或者利用 requestAnimationFrame 來(lái)不斷地檢測(cè) positionX 是否有變化。如果有變化,則立即重新渲染。

瀏覽器中的點(diǎn)擊事件處理也是典型的基于事件驅(qū)動(dòng)。在事件驅(qū)動(dòng)中,當(dāng)有事件觸發(fā)后,被觸發(fā)的事件會(huì)按順序暫時(shí)存在一個(gè)隊(duì)列中,待 JS 的同步任務(wù)執(zhí)行完成后,會(huì)從這個(gè)隊(duì)列中取出要處理的事件并進(jìn)行處理。那么具體什么時(shí)候取任務(wù)、優(yōu)先取哪些任務(wù),這就由事件循環(huán)流程來(lái)控制了。

瀏覽器中的事件循環(huán)

執(zhí)行棧與任務(wù)隊(duì)列

JS 在解析一段代碼時(shí),會(huì)將同步代碼按順序排在某個(gè)地方,即執(zhí)行棧,然后依次執(zhí)行里面的函數(shù)。當(dāng)遇到異步任務(wù)時(shí)就交給其他線程處理,待當(dāng)前執(zhí)行棧所有同步代碼執(zhí)行完成后,會(huì)從一個(gè)隊(duì)列中去取出已完成的異步任務(wù)的回調(diào)加入執(zhí)行棧繼續(xù)執(zhí)行,遇到異步任務(wù)時(shí)又交給其他線程,.....,如此循環(huán)往復(fù)。而其他異步任務(wù)完成后,將回調(diào)放入任務(wù)隊(duì)列中待執(zhí)行棧來(lái)取出執(zhí)行。

JS 按順序執(zhí)行執(zhí)行棧中的方法,每次執(zhí)行一個(gè)方法時(shí),會(huì)為這個(gè)方法生成獨(dú)有的執(zhí)行環(huán)境(上下文 context),待這個(gè)方法執(zhí)行完成后,銷毀當(dāng)前的執(zhí)行環(huán)境,并從棧中彈出此方法(即消費(fèi)完成),然后繼續(xù)下一個(gè)方法。

JavaScript事件循環(huán)的原理是什么

可見,在事件驅(qū)動(dòng)的模式下,至少包含了一個(gè)執(zhí)行循環(huán)來(lái)檢測(cè)任務(wù)隊(duì)列是否有新的任務(wù)。通過(guò)不斷循環(huán)去取出異步回調(diào)來(lái)執(zhí)行,這個(gè)過(guò)程就是事件循環(huán),而每一次循環(huán)就是一個(gè)事件周期或稱為一次 tick。

宏任務(wù)和微任務(wù)

任務(wù)隊(duì)列不只一個(gè),根據(jù)任務(wù)的種類不同,可以分為微任務(wù)(micro task)隊(duì)列和宏任務(wù)(macro task)隊(duì)列。

事件循環(huán)的過(guò)程中,執(zhí)行棧在同步代碼執(zhí)行完成后,優(yōu)先檢查微任務(wù)隊(duì)列是否有任務(wù)需要執(zhí)行,如果沒(méi)有,再去宏任務(wù)隊(duì)列檢查是否有任務(wù)執(zhí)行,如此往復(fù)。微任務(wù)一般在當(dāng)前循環(huán)就會(huì)優(yōu)先執(zhí)行,而宏任務(wù)會(huì)等到下一次循環(huán),因此,微任務(wù)一般比宏任務(wù)先執(zhí)行,并且微任務(wù)隊(duì)列只有一個(gè),宏任務(wù)隊(duì)列可能有多個(gè)。另外我們常見的點(diǎn)擊和鍵盤等事件也屬于宏任務(wù)。

下面我們看一下常見宏任務(wù)和常見微任務(wù)。

常見宏任務(wù):

  • setTimeout()

  • setInterval()

  • setImmediate()

常見微任務(wù):

  • promise.then()、promise.catch()

  • new 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"

上面的代碼將按如下順序輸出為:"同步代碼 1"、"同步代碼 2"、"同步代碼 3"、"promise.then"、"setTimeout",具體分析如下。

(1)setTimeout 回調(diào)和 promise.then 都是異步執(zhí)行的,將在所有同步代碼之后執(zhí)行;

順便提一下,在瀏覽器中 setTimeout 的延時(shí)設(shè)置為 0 的話,會(huì)默認(rèn)為 4ms,NodeJS 為 1ms。具體值可能不固定,但不是為 0。

(2)雖然 promise.then 寫在后面,但是執(zhí)行順序卻比 setTimeout 優(yōu)先,因?yàn)樗俏⑷蝿?wù);

(3)new Promise 是同步執(zhí)行的,promise.then 里面的回調(diào)才是異步的。

下面我們看一下上面代碼的執(zhí)行過(guò)程演示:

JavaScript事件循環(huán)的原理是什么

也有人這樣去理解:微任務(wù)是在當(dāng)前事件循環(huán)的尾部去執(zhí)行;宏任務(wù)是在下一次事件循環(huán)的開始去執(zhí)行。我們來(lái)看看微任務(wù)和宏任務(wù)的本質(zhì)區(qū)別是什么。

我們已經(jīng)知道,JS 遇到異步任務(wù)時(shí)會(huì)將此任務(wù)交給其他線程去處理,自己的主線程繼續(xù)往后執(zhí)行同步任務(wù)。比如 setTimeout 的計(jì)時(shí)會(huì)由瀏覽器的定時(shí)器線程來(lái)處理,待計(jì)時(shí)結(jié)束,就將定時(shí)器回調(diào)任務(wù)放入任務(wù)隊(duì)列等待主線程來(lái)取出執(zhí)行。前面我們提到,因?yàn)?JS 是單線程執(zhí)行的,所以要執(zhí)行異步任務(wù),就需要瀏覽器其他線程來(lái)輔助,即多線程是 JS 異步任務(wù)的一個(gè)明顯特征。

我們?cè)賮?lái)分析下 promise.then(微任務(wù))的處理。當(dāng)執(zhí)行到 promise.then 時(shí),V8 引擎不會(huì)將異步任務(wù)交給瀏覽器其他線程,而是將回調(diào)存在自己的一個(gè)隊(duì)列中,待當(dāng)前執(zhí)行棧執(zhí)行完成后,立馬去執(zhí)行 promise.then 存放的隊(duì)列,promise.then 微任務(wù)沒(méi)有多線程參與,甚至從某些角度說(shuō),微任務(wù)都不能完全算是異步,它只是將書寫時(shí)的代碼修改了執(zhí)行順序而已。

setTimeout 有“定時(shí)等待”這個(gè)任務(wù),需要定時(shí)器線程執(zhí)行;ajax 請(qǐng)求有“發(fā)送請(qǐng)求”這個(gè)任務(wù),需要 HTTP 線程執(zhí)行,而 promise.then 它沒(méi)有任何異步任務(wù)需要其他線程執(zhí)行,它只有回調(diào),即使有,也只是內(nèi)部嵌套的另一個(gè)宏任務(wù)。

簡(jiǎn)單小結(jié)一下微任務(wù)和宏任務(wù)的本質(zhì)區(qū)別。

  • 宏任務(wù)特征:有明確的異步任務(wù)需要執(zhí)行和回調(diào);需要其他異步線程支持。

  • 微任務(wù)特征:沒(méi)有明確的異步任務(wù)需要執(zhí)行,只有回調(diào);不需要其他異步線程支持。

定時(shí)器誤差

事件循環(huán)中,總是先執(zhí)行同步代碼后,才會(huì)去任務(wù)隊(duì)列中取出異步回調(diào)來(lái)執(zhí)行。當(dāng)執(zhí)行 setTimeout 時(shí),瀏覽器啟動(dòng)新的線程去計(jì)時(shí),計(jì)時(shí)結(jié)束后觸發(fā)定時(shí)器事件將回調(diào)存入宏任務(wù)隊(duì)列,等待 JS 主線程來(lái)取出執(zhí)行。如果這時(shí)主線程還在執(zhí)行同步任務(wù)的過(guò)程中,那么此時(shí)的宏任務(wù)就只有先掛起,這就造成了計(jì)時(shí)器不準(zhǔn)確的問(wèn)題。同步代碼耗時(shí)越長(zhǎng),計(jì)時(shí)器的誤差就越大。不僅同步代碼,由于微任務(wù)會(huì)優(yōu)先執(zhí)行,所以微任務(wù)也會(huì)影響計(jì)時(shí),假設(shè)同步代碼中有一個(gè)死循環(huán)或者微任務(wù)中遞歸不斷在啟動(dòng)其他微任務(wù),那么宏任務(wù)里面的代碼可能永遠(yuǎn)得不到執(zhí)行。所以主線程代碼的執(zhí)行效率提升是一件很重要的事情。

JavaScript事件循環(huán)的原理是什么

一個(gè)很簡(jiǎn)單的場(chǎng)景就是我們界面上有一個(gè)時(shí)鐘精確到秒,每秒更新一次時(shí)間。你會(huì)發(fā)現(xiàn)有時(shí)候秒數(shù)會(huì)直接跳過(guò) 2 秒間隔,就是這個(gè)原因。

視圖更新渲染

微任務(wù)隊(duì)列執(zhí)行完成后,也就是一次事件循環(huán)結(jié)束后,瀏覽器會(huì)執(zhí)行視圖渲染,當(dāng)然這里會(huì)有瀏覽器的優(yōu)化,可能會(huì)合并多次循環(huán)的結(jié)果做一次視圖重繪,因此視圖更新是在事件循環(huán)之后,所以并不是每一次操作 Dom 都一定會(huì)立馬刷新視圖。視圖重繪之前會(huì)先執(zhí)行 requestAnimationFrame 回調(diào),那么對(duì)于 requestAnimationFrame 是微任務(wù)還是宏任務(wù)是有爭(zhēng)議的,在這里看來(lái),它應(yīng)該既不屬于微任務(wù),也不屬于宏任務(wù)。

NodeJS 中的事件循環(huán)

JS 引擎本身不實(shí)現(xiàn)事件循環(huán)機(jī)制,這是由它的宿主實(shí)現(xiàn)的,瀏覽器中的事件循環(huán)主要是由瀏覽器來(lái)實(shí)現(xiàn),而在 NodeJS 中也有自己的事件循環(huán)實(shí)現(xiàn)。NodeJS 中也是循環(huán) + 任務(wù)隊(duì)列的流程以及微任務(wù)優(yōu)先于宏任務(wù),大致表現(xiàn)和瀏覽器是一致的。不過(guò)它與瀏覽器中也有一些差異,并且新增了一些任務(wù)類型和任務(wù)階段。接下來(lái)我們介紹下 NodeJS 中的事件循環(huán)流程。

NodeJS 中的異步方法

因?yàn)槎际腔?V8 引擎,瀏覽器中包含的異步方式在 NodeJS 中也是一樣的。另外 NodeJS 中還有一些其他常見異步形式。

  • 文件 I/O:異步加載本地文件。

  • setImmediate():與 setTimeout 設(shè)置 0ms  類似,在某些同步任務(wù)完成后立馬執(zhí)行。

  • process.nextTick():在某些同步任務(wù)完成后立馬執(zhí)行。

  • server.close、socket.on('close',...)等:關(guān)閉回調(diào)。

想象一下,如果上面的形式和 setTimeout、promise 等同時(shí)存在,如何分析出代碼的執(zhí)行順序呢?只要我們理解了 NodeJS 的事件循環(huán)機(jī)制,也就清楚了。

事件循環(huán)模型

NodeJS 的跨平臺(tái)能力和事件循環(huán)機(jī)制都是基于 Libuv 庫(kù)實(shí)現(xiàn)的,你不用關(guān)心這個(gè)庫(kù)的具體內(nèi)容。我們只需要知道 Libuv 庫(kù)是事件驅(qū)動(dòng)的,并且封裝和統(tǒng)一了不同平臺(tái)的 API 實(shí)現(xiàn)。

NodeJS   中 V8 引擎將 JS 代碼解析后調(diào)用 Node API,然后 Node API 將任務(wù)交給 Libuv 去分配,最后再將執(zhí)行結(jié)果返回給 V8 引擎。在 Libux 中實(shí)現(xiàn)了一套事件循環(huán)流程來(lái)管理這些任務(wù)的執(zhí)行,所以 NodeJS 的事件循環(huán)主要是在 Libuv 中完成的。

JavaScript事件循環(huán)的原理是什么

下面我們來(lái)看看 Libuv 中的循環(huán)是怎樣的。

事件循環(huán)各階段

在 NodeJS   中 JS 的執(zhí)行,我們主要需要關(guān)心的過(guò)程分為以下幾個(gè)階段,下面每個(gè)階段都有自己?jiǎn)为?dú)的任務(wù)隊(duì)列,當(dāng)執(zhí)行到對(duì)應(yīng)階段時(shí),就判斷當(dāng)前階段的任務(wù)隊(duì)列是否有需要處理的任務(wù)。

  • timers 階段:執(zhí)行所有 setTimeout() 和 setInterval() 的回調(diào)。

  • pending callbacks 階段:某些系統(tǒng)操作的回調(diào),如  TCP  鏈接錯(cuò)誤。除了 timers、close、setImmediate 的其他大部分回調(diào)在此階段執(zhí)行。

  • poll 階段:輪詢等待新的鏈接和請(qǐng)求等事件,執(zhí)行 I/O 回調(diào)等。V8 引擎將 JS 代碼解析并傳入 Libuv 引擎后首先進(jìn)入此階段。如果此階段任務(wù)隊(duì)列已經(jīng)執(zhí)行完了,則進(jìn)入 check 階段執(zhí)行 setImmediate 回調(diào)(如果有 setImmediate),或等待新的任務(wù)進(jìn)來(lái)(如果沒(méi)有 setImmediate)。在等待新的任務(wù)時(shí),如果有 timers 計(jì)時(shí)到期,則會(huì)直接進(jìn)入 timers 階段。此階段可能會(huì)阻塞等待。

  • check 階段:setImmediate 回調(diào)函數(shù)執(zhí)行。

  • close callbacks 階段:關(guān)閉回調(diào)執(zhí)行,如 socket.on('close', ...)。

JavaScript事件循環(huán)的原理是什么

上面每個(gè)階段都會(huì)去執(zhí)行完當(dāng)前階段的任務(wù)隊(duì)列,然后繼續(xù)執(zhí)行當(dāng)前階段的微任務(wù)隊(duì)列,只有當(dāng)前階段所有微任務(wù)都執(zhí)行完了,才會(huì)進(jìn)入下個(gè)階段。這里也是與瀏覽器中邏輯差異較大的地方,不過(guò)瀏覽器不用區(qū)分這些階段,也少了很多異步操作類型,所以不用刻意去區(qū)分兩者區(qū)別。代碼如下所示:

const fs = require('fs');
fs.readFile(__filename, (data) => {    // poll(I/O 回調(dià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秒鐘,確保上面的異步任務(wù)已經(jīng)準(zhǔn)備好了var startTime = new Date().getTime();var endTime = startTime;while(endTime - startTime < 1000) {
    endTime = new Date().getTime();
}// 最終輸出 timeout promise3 promise4 readFile promise1 promise2

另一個(gè)與瀏覽器的差異還體現(xiàn)在同一個(gè)階段里的不同任務(wù)執(zhí)行,在 timers 階段里面的宏任務(wù)、微任務(wù)測(cè)試代碼如下所示:

setTimeout(() => {  console.log('timeout1')    Promise.resolve().then(function() {    console.log('promise1')
  })
}, 0);setTimeout(() => {  console.log('timeout2')    Promise.resolve().then(function() {    console.log('promise2')
  })
}, 0);

  • 瀏覽器中運(yùn)行

    每次宏任務(wù)完成后都會(huì)優(yōu)先處理微任務(wù),輸出“timeout1”、“promise1”、“timeout2”、“promise2”。

  • NodeJS 中運(yùn)行

    因?yàn)檩敵?timeout1 時(shí),當(dāng)前正處于  timers 階段,所以會(huì)先將所有 timer 回調(diào)執(zhí)行完之后再執(zhí)行微任務(wù)隊(duì)列,即輸出“timeout1”、“timeout2”、“promise1”、“promise2”。

上面的差異可以用瀏覽器和 NodeJS 10 對(duì)比驗(yàn)證。是不是感覺(jué)有點(diǎn)反程序員?因此 NodeJS 在版本 11 之后,就修改了此處邏輯使其與瀏覽器盡量一致,也就是每個(gè) timer 執(zhí)行后都先去檢查一下微任務(wù)隊(duì)列,所以 NodeJS 11 之后的輸出已經(jīng)和瀏覽器一致了。

nextTick、setImmediate 和 setTimeout

實(shí)際項(xiàng)目中我們常用 Promise 或者 setTimeout 來(lái)做一些需要延時(shí)的任務(wù),比如一些耗時(shí)計(jì)算或者日志上傳等,目的是不希望它的執(zhí)行占用主線程的時(shí)間或者需要依賴整個(gè)同步代碼執(zhí)行完成后的結(jié)果。

NodeJS 中的 process.nextTick() 和 setImmediate() 也有類似效果。其中 setImmediate() 我們前面已經(jīng)講了是在 check 階段執(zhí)行的,而 process.nextTick() 的執(zhí)行時(shí)機(jī)不太一樣,它比 promise.then() 的執(zhí)行還早,在同步任務(wù)之后,其他所有異步任務(wù)之前,會(huì)優(yōu)先執(zhí)行 nextTick??梢韵胂笫前?nextTick 的任務(wù)放到了當(dāng)前循環(huán)的后面,與 promise.then() 類似,但比 promise.then() 更前面。意思就是在當(dāng)前同步代碼執(zhí)行完成后,不管其他異步任務(wù),先盡快執(zhí)行 nextTick。如下面的代碼,因此這里的 nextTick 其實(shí)應(yīng)該更符合“setImmediate”這個(gè)命名才對(duì)。

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

接下來(lái)我們?cè)賮?lái)看看 setImmediate 和 setTimeout,它們是屬于不同的執(zhí)行階段了,分別是 timers 階段和 check 階段。

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

分析上面代碼,第一輪循環(huán)后,分別將 setTimeout   和 setImmediate 加入了各自階段的任務(wù)隊(duì)列。第二輪循環(huán)首先進(jìn)入  timers 階段,執(zhí)行定時(shí)器隊(duì)列回調(diào),然后  pending callbacks 和 poll 階段沒(méi)有任務(wù),因此進(jìn)入check 階段執(zhí)行 setImmediate 回調(diào)。所以最后輸出為“timeout”、“setImmediate”。當(dāng)然這里還有種理論上的極端情況,就是第一輪循環(huán)結(jié)束后耗時(shí)很短,導(dǎo)致 setTimeout 的計(jì)時(shí)還沒(méi)結(jié)束,此時(shí)第二輪循環(huán)則會(huì)先執(zhí)行 setImmediate 回調(diào)。

再看這下面一段代碼,它只是把上一段代碼放在了一個(gè) I/O 任務(wù)回調(diào)中,它的輸出將與上一段代碼相反。

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

如上面代碼所示:

  • 第一輪循環(huán)沒(méi)有需要執(zhí)行的異步任務(wù)隊(duì)列;

  • 第二輪循環(huán) timers 等階段都沒(méi)有任務(wù),只有 poll 階段有 I/O 回調(diào)任務(wù),即輸出“readFile”;

  • 參考前面事件階段的說(shuō)明,接下來(lái),poll 階段會(huì)檢測(cè)如果有 setImmediate 的任務(wù)隊(duì)列則進(jìn)入 check 階段,否則再進(jìn)行判斷,如果有定時(shí)器任務(wù)回調(diào),則回到 timers 階段,所以應(yīng)該進(jìn)入 check 階段執(zhí)行 setImmediate,輸出“setImmediate”;

  • 然后進(jìn)入最后的 close callbacks 階段,本次循環(huán)結(jié)束;

  • 最后進(jìn)行第三輪循環(huán),進(jìn)入 timers 階段,輸出“timeout”。

所以最終輸出“setImmediate”在“timeout”之前??梢娺@兩者的執(zhí)行順序與當(dāng)前執(zhí)行的階段有關(guān)系。

以上就是“JavaScript事件循環(huán)的原理是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

本文名稱:JavaScript事件循環(huán)的原理是什么
網(wǎng)站網(wǎng)址:http://bm7419.com/article44/igsiee.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、搜索引擎優(yōu)化域名注冊(cè)、虛擬主機(jī)營(yíng)銷型網(wǎng)站建設(shè)、商城網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司