摘要:如果沒有其他異步任務(wù)要處理比如到期的定時器,會一直停留在這個階段,等待請求返回結(jié)果。執(zhí)行的執(zhí)行事件關(guān)閉請求的,例如事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過上述的階段。因此,才會早于執(zhí)行。
概念 同步任務(wù)(Synchronous)
在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù)
異步任務(wù)(Asynchronous)不進入主線程,而是進入“任務(wù)隊列”的任務(wù),只有主線執(zhí)行棧清空,異步任務(wù)才進入主線執(zhí)行棧執(zhí)行
任務(wù)隊列(Task Queue)包含有異步任務(wù)的隊列,包括“宏任務(wù)”與“微任務(wù)”
宏任務(wù)(Macrotasks / Task)創(chuàng)建文檔對象、解析 HTML、執(zhí)行主線程代碼(script)
執(zhí)行各種事件:頁面加載、輸入、點擊
setTimout,setInterval,setImmediate
I/O,Ajax,UI rendering
微任務(wù)(Microtasks / Jobs)process.nextTick
Promise.then
Object.observe(已廢棄)
MutationObserver
事件循環(huán)(Event Loop)事件循環(huán)是js實現(xiàn)異步的一種方法,也是js的執(zhí)行機制
JavaScript 主線程會在執(zhí)行棧清空后,讀取任務(wù)隊列,入棧第一個宏任務(wù),主線程執(zhí)行完該任務(wù)后又會先檢查微任務(wù)隊列并完成里面的所有微任務(wù),包括新創(chuàng)建的微任務(wù),完成一次事件循環(huán)。之后再次去讀取任務(wù)隊列,不斷循環(huán)
注意:
每次循環(huán)只會入棧一個宏任務(wù),所以多個宏任務(wù)需要多次事件循環(huán)才能執(zhí)行完
每次循環(huán)會執(zhí)行所有的微任務(wù),所以每次循環(huán)結(jié)束后微任務(wù)隊列被清空
timers:
執(zhí)行 setTimeout 和 setInterval 中到期的 callback
I/O callbacks:
除了以下操作的回調(diào)函數(shù),其他的回調(diào)函數(shù)都在這個階段執(zhí)行。
setTimeout,setInterval,setImmediate 的 callback
用于執(zhí)行 close 事件(關(guān)閉請求)的 callback,例如 socket.on("close", callback)
idle, prepare:
libuv 內(nèi)部調(diào)用
poll:
最為重要的階段,用于等待還未返回的 I/O 事件,比如服務(wù)器的回應(yīng)、用戶移動鼠標等等
這個階段的時間會比較長。如果沒有其他異步任務(wù)要處理(比如到期的定時器),會一直停留在這個階段,等待 I/O 請求返回結(jié)果。
check:
執(zhí)行 setImmediate 的 callback
close callbacks:
執(zhí)行 close 事件(關(guān)閉請求)的 callback,例如 socket.on("close", callback)
事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過上述的階段。 每個階段都有自己的 callback 隊列,每當進入某個階段,都會從所屬的隊列中取出callback來執(zhí)行,當隊列為空或者被執(zhí)行callback的數(shù)量達到系統(tǒng)的最大數(shù)量時,進入下一階段。這六個階段都執(zhí)行完畢稱為一輪循環(huán)
注意:
不同于瀏覽器的是,在每個階段完成后,microTask隊列就會被執(zhí)行,而不是MacroTask任務(wù)完成后。
每個階段完成后,微任務(wù)隊列就會被執(zhí)行。
如果在timers階段執(zhí)行時創(chuàng)建了setImmediate則會在此輪循環(huán)的check階段執(zhí)行,如果在timers階段創(chuàng)建了 setTimeout,由于timers已取出完畢,則會進入下輪循環(huán),check階段創(chuàng)建timers任務(wù)同理
遞歸的調(diào)用 process.nextTick 會導(dǎo)致 I/O starving,官方推薦使用 setImmediate
瀏覽器環(huán)境下,microtask 的任務(wù)隊列是每個 macrotask 執(zhí)行完之后執(zhí)行
Node.js中,microtask 會在事件循環(huán)的各個階段之間執(zhí)行,也就是一個階段執(zhí)行完畢,就會去執(zhí)行 microtask 隊列的任務(wù)
setTimeout(()=>{ console.log("timer1") Promise.resolve().then(function() { console.log("promise1") }) }, 0) setTimeout(()=>{ console.log("timer2") Promise.resolve().then(function() { console.log("promise2") }) }, 0) // 瀏覽器輸出: // time1 // promise1 // time2 // promise2 // Node輸出: // time1 // time2 // promise1 // promise2定時器 setTimeout(callback, time)
經(jīng)過指定時間后,把要執(zhí)行的任務(wù) callback 加入到任務(wù)隊列中
因為JS是單線程,任務(wù)要一個一個執(zhí)行,如果前面的任務(wù)需要的時間太久,那么只能等著,導(dǎo)致真正的延遲時間可能遠遠大于指定時間(time ms)
setTimeout(callback, 0)指定某個任務(wù) callback 在主線程最早可得的空閑時間執(zhí)行,意思就是不用再等多少秒了,只要主線程執(zhí)行棧內(nèi)的同步任務(wù)全部執(zhí)行完成,棧為空就馬上執(zhí)行
0ms 實際上是不可能的,在瀏覽器中 setTimeout() / setInterval() 的每調(diào)用一次定時器的最小間隔 >=4ms,這通常是由于函數(shù)嵌套導(dǎo)致(嵌套層級達到一定深度),或者是由于已經(jīng)執(zhí)行的 setInterval 的回調(diào)函數(shù)阻塞導(dǎo)致的
在 Node.JS 環(huán)境為 1ms,但也取決于系統(tǒng)當時的狀況
setTimeout(function () { console.log("1"); }, 0) console.log(2) // 輸出 2 1setInterval(callback, time)
每過指定時間(time ms),會有 callback 進入任務(wù)隊列。
若 callback 執(zhí)行時間超過了指定時間,那么就會導(dǎo)致 callback 連續(xù)執(zhí)行,完全看不出來有時間間隔了
setImmediate(callback)Node.JS 特有定時器,在事件循環(huán)的 check 階段執(zhí)行
process.nextTick(callback)Node.JS 特有定時器,在事件循環(huán)各個階段結(jié)束后執(zhí)行
從技術(shù)上講,它不是事件循環(huán)的一部分
同循環(huán)下 process.nextTick 會優(yōu)于 Promise.then
Promise.resolve().then(() => console.log(1)); process.nextTick(() => console.log(2)); // 輸出 2 1注意
連續(xù)的 setTimeout,setImmediate 在再 timer 階段的執(zhí)行順序是不確定的,取決于系統(tǒng)當時的狀況
但是把 setTimeout,setImmediate 放到一個 I/O 回調(diào)里面,就一定是 setImmediate 先執(zhí)行,因為 poll 階段后面就是 check 階段
setImmediate(() => { console.log("timer1") Promise.resolve().then(function () { console.log("promise1") }) }) setTimeout(() => { console.log("timer2") Promise.resolve().then(function () { console.log("promise2") }) }, 0) // Node輸出: // timer1 timer2 // promise1 或者 promise2 // timer2 timer1 // promise2 promise1
fs.readFile("test.js", () => { setTimeout(() => console.log(1)); setImmediate(() => console.log(2)); }) // 輸出 2 1 // 先進入 I/O callbacks 階段,然后是 check 階段,最后才是 下一次事件循環(huán)的 timers 階段。因此,setImmediate 才會早于setTimeout 執(zhí)行。示例
console.log(0) new Promise(function(resolve) { console.log(1); resolve(); }).then(function() { console.log(2) }) setTimeout(function() { console.log(3); new Promise(function(resolve) { console.log(4); resolve(); }).then(function() { console.log(5) }) }) new Promise(function(resolve) { console.log(6); resolve(); }).then(function() { console.log(7) }) setTimeout(function() { console.log(8); new Promise(function(resolve) { console.log(9); resolve(); }).then(function() { console.log(10) }) }) console.log(11)瀏覽器環(huán)境
第一輪事件循環(huán)宏任務(wù)
開始執(zhí)行代碼,遇到 console.log 輸出 0
接著遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 1
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
接著遇到 setTimeout 其回調(diào)函數(shù)作為異步任務(wù)進入“宏任務(wù)隊列”
接著遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 6
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
接著遇到 setTimeout 其回調(diào)函數(shù)作為異步任務(wù)進入“宏任務(wù)隊列”
最后遇到 console.log 輸出 11
此時第一輪事件循環(huán)宏任務(wù)結(jié)束,依次輸出 0 1 6 11
第一輪事件循環(huán)微任務(wù)
執(zhí)行注冊在“微任務(wù)隊列”里的微任務(wù),遇到 then 執(zhí)行其回調(diào)輸出 2
遇到 then 執(zhí)行其回調(diào)輸出 7
此時第一輪事件循環(huán)微任務(wù)結(jié)束,依次輸出 2 7
第一輪事件循環(huán)結(jié)束,此時“微任務(wù)隊列”已被清空,“宏任務(wù)隊列”里有兩個 setTimeout 的回調(diào)函數(shù),第一個 setTimeout 被送入主線程執(zhí)行棧
第二輪事件循環(huán)宏任務(wù)
開始執(zhí)行第一個 setTimeout 回調(diào)函數(shù)
遇到 console.log 輸出 3
遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 4
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
此時第二輪事件循環(huán)宏任務(wù)結(jié)束,依次輸出 3 4
第二輪事件循環(huán)微任務(wù)
執(zhí)行注冊在“微任務(wù)隊列”里的微任務(wù),遇到 then 執(zhí)行其回調(diào)輸出 5
此時第二輪事件循環(huán)微任務(wù)結(jié)束,輸出 5
第二輪事件循環(huán)結(jié)束,此時“微任務(wù)隊列”已被清空,“宏任務(wù)隊列”里剩下一個 setTimeout 的回調(diào)函數(shù),其被送入主線程執(zhí)行棧
第三輪事件循環(huán)宏任務(wù)
開始執(zhí)行第二個 setTimeout 回調(diào)函數(shù)
遇到 console.log 輸出 8
遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 9
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
此時第三輪事件循環(huán)宏任務(wù)結(jié)束,依次輸出 8 9
第三輪事件循環(huán)微任務(wù)
執(zhí)行注冊在“微任務(wù)隊列”里的微任務(wù),遇到 then 執(zhí)行其回調(diào)輸出 10
此時第三輪事件循環(huán)微任務(wù)結(jié)束,輸出 10
第三輪事件循環(huán)結(jié)束,此時“微任務(wù)隊列”已被清空,“宏任務(wù)隊列”已被清空
至此整段代碼執(zhí)行完畢,完整輸出結(jié)果為:0 1 6 11 2 7 3 4 5 8 9 10
Node.JS 環(huán)境Node.JS 環(huán)境下任務(wù)隊列有層級之分,按層級執(zhí)行任務(wù)隊列
第一輪事件循環(huán)宏任務(wù)
開始執(zhí)行代碼,遇到 console.log 輸出 0
接著遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 1
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
接著遇到 setTimeout 其回調(diào)函數(shù)注冊到 timer 階段
接著遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 6
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
接著遇到 setTimeout 其回調(diào)函數(shù)注冊到 timer 階段
最后遇到 console.log 輸出 11
此時第一輪事件循環(huán)宏任務(wù)結(jié)束,依次輸出 0 1 6 11
第一輪事件循環(huán)微任務(wù)
執(zhí)行注冊在“微任務(wù)隊列”里的微任務(wù),遇到 then 執(zhí)行其回調(diào)輸出 2
遇到 then 執(zhí)行其回調(diào)輸出 7
此時第一輪事件循環(huán)微任務(wù)結(jié)束,依次輸出 2 7
第一輪事件循環(huán)結(jié)束,此時“微任務(wù)隊列”已被清空,timer 隊列里有兩個 setTimeout 的回調(diào)函數(shù)
第二輪事件循環(huán) timer 階段
執(zhí)行第一個 setTimeout 回調(diào)函數(shù)
遇到 console.log 輸出 3
遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 4
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
執(zhí)行第二個 setTimeout 回調(diào)函數(shù)
遇到 console.log 輸出 8
遇到 new Promise 其回調(diào)函數(shù)作為同步任務(wù)直接執(zhí)行
遇到 console.log 輸出 9
遇到 then 回調(diào)函數(shù)作為異步任務(wù)進入“微任務(wù)隊列”
此時第二輪事件循 timer 階段結(jié)束,依次輸出 3 4 8 9
第二輪事件循環(huán) timer 階段微任務(wù)
執(zhí)行注冊在“微任務(wù)隊列”里的微任務(wù)
遇到 then 執(zhí)行其回調(diào)輸出 5
遇到 then 執(zhí)行其回調(diào)輸出 10
此時第二輪事件循環(huán) timer 階段微任務(wù)結(jié)束,輸出 5 10
第二輪事件循環(huán)結(jié)束,至此整段代碼執(zhí)行完畢,完整輸出結(jié)果為:0 1 6 11 2 7 3 4 8 9 5 10
參考文章阮一峰 - JavaScript 運行機制詳解:再談Event Loop阮一峰 - Node 定時器詳解
Philip Roberts - Help,I’m stuck in an event loop
lynnelv - 深入理解js事件循環(huán)機制(Node.js篇)
這一次,徹底弄懂 JavaScript 執(zhí)行機制
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/101469.html
摘要:知識點聲明的變量在預(yù)解析的時候只執(zhí)行聲明,不會執(zhí)行定義,默認值是。聲明的函數(shù)在預(yù)解析的時候會提前聲明并且會同時定義。 showImg(https://segmentfault.com/img/bVbnY76?w=2500&h=1250); 知識點 var 聲明的變量在預(yù)解析的時候只執(zhí)行聲明,不會執(zhí)行定義,默認值是 undefined。 function 聲明的函數(shù)在預(yù)解析的時候會...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進步歡迎點贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...
閱讀 1913·2021-11-25 09:43
閱讀 1415·2021-11-22 14:56
閱讀 3285·2021-11-22 09:34
閱讀 2018·2021-11-15 11:37
閱讀 2263·2021-09-01 10:46
閱讀 1403·2019-08-30 15:44
閱讀 2299·2019-08-30 13:15
閱讀 2400·2019-08-29 13:07