摘要:新加了一個微任務(wù)和一個宏任務(wù)在當前執(zhí)行棧的尾部下一次之前觸發(fā)回調(diào)函數(shù)。階段這個階段主要執(zhí)行一些系統(tǒng)操作帶來的回調(diào)函數(shù),如錯誤,如果嘗試鏈接時出現(xiàn)錯誤,一些會把這個錯誤報告給。
JavaScript引擎又稱為JavaScript解釋器,是JavaScript解釋為機器碼的工具,分別運行在瀏覽器和Node中。而根據(jù)上下文的不同,Event loop也有不同的實現(xiàn):其中Node使用了libuv庫來實現(xiàn)Event loop; 而在瀏覽器中,html規(guī)范定義了Event loop,具體的實現(xiàn)則交給不同的廠商去完成。
瀏覽器中的Event Loops根據(jù)2017年新版的HTML規(guī)范HTML Standard,瀏覽器包含2類事件循環(huán):browsing contexts 和 web workers。
browsing contexts中有一個或多個Task Queue,即MacroTask Queue,僅有一個Job Queue,即MicroTask Queue。
macrotask queue(宏任務(wù),不妨稱為A)
setTimeout
setInterval
setImmediate(node獨有)
requestAnimationFrame
I/O
UI rendering
microtask queue(微任務(wù),不妨稱為I)
process.nextTick(node獨有)
Promises
Object.observe(廢棄)
MutationObserver
這兩個任務(wù)隊列執(zhí)行順序:
取1個A中的task,執(zhí)行之。
把所有I順序執(zhí)行完,再取A中的下一個任務(wù)。
為什么promise.then的回調(diào)比setTimeout先執(zhí)行
代碼開始執(zhí)行時,所有這些代碼在A中,形成一個執(zhí)行棧(execution context stack),取出來執(zhí)行之。
遇到setTimeout,則加到A中,遇到promise.then,則加到I中。
等整個執(zhí)行棧執(zhí)行完,取I中的任務(wù)。
(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })() // 1 // 2 // 3 // 5 // 4
//瀏覽器渲染步驟:Structure(構(gòu)建 DOM) ->Layout(排版)->Paint(繪制) //新的異步任務(wù)將在下一次被執(zhí)行,因此就不會存在阻塞。 button.addEventListener("click", () => { setTimeout(fn, 0) })
V8源碼
https://github.com/v8/v8/blob...
https://github.com/v8/v8/blob...
而在Node.js中,microtask會在事件循環(huán)的各個階段之間執(zhí)行,也就是一個階段執(zhí)行完畢,就會去執(zhí)行microtask隊列的任務(wù)。
node新加了一個微任務(wù)process.nextTick和一個宏任務(wù)setImmediate.
process.nextTick在當前"執(zhí)行棧"的尾部(下一次Event Loop之前)觸發(fā)回調(diào)函數(shù)。也就是說,它指定的任務(wù)總是發(fā)生在所有異步任務(wù)之前。
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log("TIMEOUT FIRED"); }, 0) // 1 // 2 // TIMEOUT FIREDsetImmediate
setImmediate方法則是在當前"任務(wù)隊列"的尾部添加事件,也就是說,它指定的任務(wù)總是在下一次Event Loop時執(zhí)行,這與setTimeout(fn, 0)很像。
setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log("TIMEOUT FIRED"); }, 0); //不確定
遞歸的調(diào)用process.nextTick()會導致I/O starving,官方推薦使用setImmediate()
process.nextTick(function foo() { process.nextTick(foo); }); //FATAL ERROR: invalid table size Allocation failed - JavaScript heap out of memory
process.nextTick也會放入microtask quque,為什么優(yōu)先級比promise.then高呢
在Node中,_tickCallback在每一次執(zhí)行完TaskQueue中的一個任務(wù)后被調(diào)用,而這個_tickCallback中實質(zhì)上干了兩件事:
nextTickQueue中所有任務(wù)執(zhí)行掉(長度最大1e4,Node版本v6.9.1)
第一步執(zhí)行完后執(zhí)行_runMicrotasks函數(shù),執(zhí)行microtask中的部分(promise.then注冊的回調(diào))所以很明顯process.nextTick > promise.then”
node.js的特點是事件驅(qū)動,非阻塞單線程。當應用程序需要I/O操作的時候,線程并不會阻塞,而是把I/O操作交給底層庫(LIBUV)。此時node線程會去處理其他任務(wù),當?shù)讓訋焯幚硗闕/O操作后,會將主動權(quán)交還給Node線程,所以Event Loop的用處是調(diào)度線程,例如:當?shù)讓訋焯幚鞩/O操作后調(diào)度Node線程處理后續(xù)工作,所以雖然node是單線程,但是底層庫處理操作依然是多線程。
根據(jù)Node.js官方介紹,每次事件循環(huán)都包含了6個階段,對應到 libuv 源碼中的實現(xiàn),如下圖所示
timers :這個階段執(zhí)行timer(setTimeout、setInterval)的回調(diào)
I/O callbacks:執(zhí)行一些系統(tǒng)調(diào)用錯誤,比如網(wǎng)絡(luò)通信的錯誤回調(diào)
idle, prepare :僅node內(nèi)部使用
poll :獲取新的I/O事件, 適當?shù)臈l件下node將阻塞在這里
check :執(zhí)行 setImmediate() 的回調(diào)
close callbacks :執(zhí)行 socket 的 close 事件回調(diào)
timers 是事件循環(huán)的第一個階段,Node 會去檢查有無已過期的timer,如果有則把它的回調(diào)壓入timer的任務(wù)隊列中等待執(zhí)行,事實上,Node 并不能保證timer在預設(shè)時間到了就會立即執(zhí)行,因為Node對timer的過期檢查不一定靠譜,它會受機器上其它運行程序影響,或者那個時間點主線程不空閑。但是把它們放到一個I/O回調(diào)里面,就一定是 setImmediate() 先執(zhí)行,因為poll階段后面就是check階段。
I/O callbacks 階段這個階段主要執(zhí)行一些系統(tǒng)操作帶來的回調(diào)函數(shù),如 TCP 錯誤,如果 TCP 嘗試鏈接時出現(xiàn) ECONNREFUSED 錯誤 ,一些 *nix 會把這個錯誤報告給 Node.js。而這個錯誤報告會先進入隊列中,然后在 I/O callbacks 階段執(zhí)行。
poll 階段poll 階段主要有2個功能:
處理 poll 隊列的事件
當有已超時的 timer,執(zhí)行它的回調(diào)函數(shù)
even loop將同步執(zhí)行poll隊列里的回調(diào),直到隊列為空或執(zhí)行的回調(diào)達到系統(tǒng)上限(上限具體多少未詳),接下來even loop會去檢查有無預設(shè)的setImmediate(),分兩種情況:
若有預設(shè)的setImmediate(), event loop將結(jié)束poll階段進入check階段,并執(zhí)行check階段的任務(wù)隊列
若沒有預設(shè)的setImmediate(),event loop將阻塞在該階段等待
注意一個細節(jié),沒有setImmediate()會導致event loop阻塞在poll階段,這樣之前設(shè)置的timer豈不是執(zhí)行不了了?所以咧,在poll階段event loop會有一個檢查機制,檢查timer隊列是否為空,如果timer隊列非空,event loop就開始下一輪事件循環(huán),即重新進入到timer階段。
check 階段setImmediate()的回調(diào)會被加入check隊列中, 從event loop的階段圖可以知道,check階段的執(zhí)行順序在poll階段之后。
close 階段突然結(jié)束的事件的回調(diào)函數(shù)會在這里觸發(fā),如果 socket.destroy(),那么 close 會被觸發(fā)在這個階段,也有可能通過 process.nextTick() 來觸發(fā)。
示例setTimeout(()=>{ console.log("timer1") Promise.resolve().then(function() { console.log("promise1") }) }, 0) setTimeout(()=>{ console.log("timer2") Promise.resolve().then(function() { console.log("promise2") }) }, 0) /*瀏覽器中 timer1 promise1 timer2 promise2 */ /*node中 timer1 timer2 promise1 promise2 */
const fs = require("fs") fs.readFile("test.txt", () => { console.log("readFile") setTimeout(() => { console.log("timeout") }, 0) setImmediate(() => { console.log("immediate") }) }) /* readFile immediate timeout */
更多示例
libuv源碼
https://github.com/libuv/libu...
HTML5標準規(guī)定了setTimeout()的第二個參數(shù)的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設(shè)為10毫秒。另外,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分),通常不會立即執(zhí)行,而是每16毫秒執(zhí)行一次。這時使用requestAnimationFrame()的效果要好于setTimeout()
客戶端可能實現(xiàn)了一個包含鼠標鍵盤事件的任務(wù)隊列,還有其他的任務(wù)隊列,而給鼠標鍵盤事件的任務(wù)隊列更高優(yōu)先級,例如75%的可能性執(zhí)行它。這樣就能保證流暢的交互性,而且別的任務(wù)也能執(zhí)行到了。但是,同一個任務(wù)隊列中的任務(wù)必須按先進先出的順序執(zhí)行。
用戶點擊與button.click()的區(qū)別:
用戶點擊:依次執(zhí)行l(wèi)istener。瀏覽器并不實現(xiàn)知道有幾個 listener,因此它發(fā)現(xiàn)一個執(zhí)行一個,執(zhí)行完了再看后面還有沒有。
click:同步執(zhí)行l(wèi)istener。 click方法會先采集有哪些 listener,再依次觸發(fā)。
示例詳情
參考資料
Promise的隊列與setTimeout的隊列有何關(guān)聯(lián)?
瀏覽器的 Event Loop
Event Loops
深入理解js事件循環(huán)機制(Node.js篇)
JavaScript 運行機制詳解:再談Event Loop
Node.js 事件循環(huán),定時器和 process.nextTick()
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100832.html
摘要:曾經(jīng)的理解首先,是單線程語言,也就意味著同一個時間只能做一件事,那么為什么不是多線程呢這樣還能提高效率啊假定同時有兩個線程,一個線程在某個節(jié)點上編輯了內(nèi)容,而另一個線程刪除了這個節(jié)點,這時瀏覽器就很懵逼了,到底以執(zhí)行哪個操作呢所以,設(shè)計者把 Event Loop曾經(jīng)的理解 首先,JS是單線程語言,也就意味著同一個時間只能做一件事,那么 為什么JavaScript不是多線程呢?這樣還能提...
摘要:前言前幾天在理解的事件環(huán)機制中引發(fā)了我對瀏覽器里的好奇。接下來理解瀏覽器中的,先看一張圖堆和棧堆是用戶主動請求而劃分出來的內(nèi)存區(qū)域,比如你,就是將一個對象存入堆中,可以理解為存對象。廢話不多說,直接上圖個人理解。參考資料運行機制詳解再談 前言 前幾天在理解node的事件環(huán)機制中引發(fā)了我對瀏覽器里Event Loop的好奇。我們都知道javascript是單線程的,任務(wù)是需要一個一個按順...
摘要:深入理解引擎的執(zhí)行機制靈魂三問為什么是單線程的為什么需要異步單線程又是如何實現(xiàn)異步的呢中的中的說說首先請牢記點是單線程語言的是的執(zhí)行機制。 深入理解JS引擎的執(zhí)行機制 1.靈魂三問 : JS為什么是單線程的? 為什么需要異步? 單線程又是如何實現(xiàn)異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說說setTimeout 首先,請牢記2...
摘要:瀏覽器和中并不一樣,瀏覽器的是在中定義的規(guī)范,而中則由庫實現(xiàn)。整個的這種運行機制又稱為事件循環(huán)例子了解瀏覽器的后,查看下面例子,猜測瀏覽器是怎么輸出的瀏覽器輸出中的在內(nèi)部有這樣一個事件環(huán)機制。在啟動時會初始化事件環(huán)。執(zhí)行和中到期的。 大家都知道,javascript是一門單線程語言,因此為了實現(xiàn)主線程的不阻塞,Event Loop這樣的方案應運而生。 瀏覽器和node中Event lo...
摘要:但是導致了很明顯的性能問題。上述兩個例子其實是在這個中找到的,第一個使用的版本是,這個版本的實現(xiàn)是采用了,而后因為的里的有,于是尤雨溪更改了實現(xiàn),換成了,也就是后一個所使用的。后來尤雨溪了解到是將回調(diào)放入的隊列。 結(jié)論 對于event loop 可以抽象成一段簡單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
閱讀 2575·2021-11-22 09:34
閱讀 3554·2021-11-15 11:37
閱讀 2357·2021-09-13 10:37
閱讀 2116·2021-09-04 16:40
閱讀 1600·2021-09-02 15:40
閱讀 2468·2019-08-30 13:14
閱讀 3338·2019-08-29 13:42
閱讀 1914·2019-08-29 13:02