深入理解 js 事件循環(huán)機(jī)制(瀏覽器篇)

#?深入理解?js?事件循環(huán)機(jī)制(瀏覽器篇)

javascript?eventloop

-?拋在前面的問題:

??單線程如何做到異步树灶?

??事件循環(huán)的過程是怎樣的喊式?

??macrotask?和?microtask?是什么硼莽,它們有何區(qū)別橄杨?

-?單線程和異步

??提到?js莺葫,就會(huì)想到單線程片习,異步捌肴,那么單線程是如何做到異步的呢?概念先行藕咏,先要了解下單線程和異步之間的關(guān)系状知。

??1.?js?的任務(wù)分為?【同步】?和?【異步】?兩種。

??2.?它們的處理方式也不同孽查,同步任務(wù)是直接在主線程上排隊(duì)執(zhí)行饥悴,異步任務(wù)則會(huì)被放到【任務(wù)隊(duì)列】中。

??3.?若有多個(gè)任務(wù)(異步任務(wù))則要在【任務(wù)隊(duì)列】中排隊(duì)等待盲再,【任務(wù)隊(duì)列】類似一個(gè)緩沖區(qū)西设,任務(wù)完成會(huì)被移到【調(diào)用棧(call?stack)】,然后由主線程執(zhí)行【調(diào)用棿鹋螅】的任務(wù)贷揽。

??4.?單線程是指?js?引擎中負(fù)責(zé)解析執(zhí)行?js?代碼的線程只有一個(gè)(主線程),即每次只能做一件事梦碗,而我們知道一個(gè)?ajax?請(qǐng)求禽绪,主線程在等待它響應(yīng)的同時(shí)是會(huì)去做其它事的,瀏覽器先在【事件表】注冊(cè)?ajax?的回調(diào)函數(shù)洪规,響應(yīng)回來后回調(diào)函數(shù)被添加到【任務(wù)隊(duì)列】中等待執(zhí)行印屁,不會(huì)造成線程阻塞,所以說?js?處理?ajax?請(qǐng)求的方式是異步的斩例。

??-?總而言之雄人,檢查【調(diào)用棧】是否為空樱拴,以及確定把哪個(gè)?task?加入調(diào)用棧的這個(gè)過程就是事件循環(huán)柠衍,而?[js?實(shí)現(xiàn)異步的核心就是事件循環(huán)]洋满。

-?調(diào)用棧和任務(wù)隊(duì)列

??-?調(diào)用棧是一個(gè)棧結(jié)構(gòu),函數(shù)調(diào)用會(huì)形成一個(gè)棧幀珍坊,幀中包含了當(dāng)前執(zhí)行函數(shù)的參數(shù)和局部變量等上下文信息牺勾,函數(shù)執(zhí)行完后,它的執(zhí)行上下文會(huì)從棧中彈出阵漏。

??-?任務(wù)隊(duì)列?是用來存放任務(wù)的驻民,如果存放的是異步任務(wù),當(dāng)任務(wù)完成之后(比如定時(shí)器到了時(shí)間)履怯,就會(huì)被移入到?調(diào)用棧回还,等待?主線程?順序執(zhí)行調(diào)用棧的每一個(gè)事件。

-?事件循環(huán)

??1.?關(guān)于事件循環(huán)叹洲,HTML?規(guī)范的介紹

?????There?must?be?at?least?one?event?loop?per?user?agent,?and?at?most?one?event?loop?per?unit?of?related?similar-origin?browsing?contexts.

?????An?event?loop?has?one?or?more?task?queues.

?????Each?task?is?defined?as?coming?from?a?specific?task?source.

?????=>?從規(guī)范理解柠硕,瀏覽器至少有一個(gè)事件循環(huán),一個(gè)事件循環(huán)至少有一個(gè)任務(wù)隊(duì)列(一個(gè)宏任務(wù)的任務(wù)隊(duì)列?macrotask)运提,每個(gè)外任務(wù)都有自己的分組蝗柔,瀏覽器會(huì)為不同的任務(wù)組設(shè)置優(yōu)先級(jí)。

-?macrotask?&?microtask

??規(guī)范有提到兩個(gè)概念民泵,但沒有詳細(xì)介紹癣丧,查閱一些資料大概可總結(jié)如下:

??1.?(宏任務(wù))macrotask:包含執(zhí)行整體的?js?代碼,事件回調(diào)栈妆,XHR?回調(diào)胁编,定時(shí)器(setTimeout/setInterval/setImmediate),IO?操作鳞尔,UI?render

??2.?(微任務(wù))microtask:更新應(yīng)用程序狀態(tài)的任務(wù)嬉橙,包括?promise?回調(diào),MutationObserver铅檩,process.nextTick憎夷,Object.observe

其中?setImmediate?和?process.nextTick?是?nodejs?的實(shí)現(xiàn)莽鸿,在?nodejs?篇會(huì)詳細(xì)介紹昧旨。

-?事件處理過程

??關(guān)于?macrotask?和?microtask?的理解,光這樣看會(huì)有些晦澀難懂祥得,結(jié)合事件循壞的機(jī)制理解清晰很多兔沃,下面這張圖可以說是介紹得非常清楚了。

??-?event-loop?事件循環(huán)機(jī)制.jpg

總結(jié)起來级及,一次事件循環(huán)的步驟包括:

1.?檢查?macrotask?隊(duì)列是否為空乒疏,非空則直接步驟?2,為空則直接步驟?3

2.?執(zhí)行?macrotask?中的一個(gè)任務(wù)

3.?繼續(xù)檢查?microtask?隊(duì)列是否為空饮焦,若有則直接步驟?4怕吴,否則直接步驟?5

4.?取出?microtask?中的任務(wù)執(zhí)行窍侧,執(zhí)行完成返回到步驟?3

5.?執(zhí)行視圖更新

???mactotask?&?microtask?的執(zhí)行順序?(一般事件循環(huán)執(zhí)行一次瀏覽器會(huì)有一個(gè)?undefined)

-?看一段代碼感受下:

??console.log('start')

??var?time1?=?setTimeout(function()?{

??console.log('setTimeout')

??},?0);

??var?time2?=?setTimeout(function()?{

??console.log('setTimeout2')

??},?0);

??new?Promise(resolve?=>?{

????resolve();

????console.log(1);

??}).then(function()?{

??console.log('promise1')

??}).then(function()?{

??console.log('promise2')

??})

??console.log('end')

console?輸出的?log?順序是什么?結(jié)合上述的步驟分析转绷,系不系?so?easy~:

????start????????VM110:1?

????1????????????VM110:13

????end??????????VM110:19

????promise1?????VM110:15

????promise2?????VM110:17

????undefined???//其實(shí)這里就是瀏覽器的多線程機(jī)制?可能是ui渲染線程伟件。

????setTimeout????VM110:4

????setTimeout2???VM110:8

*?過程詳解:?

??1.?首先,全局代碼(main())壓入調(diào)用棧執(zhí)行议经,打印?start斧账;

??2.?接下來?time1?壓入?macrotask?隊(duì)列,緊接著?time2?壓入?macrotask?隊(duì)列中煞肾;

??3.?promise.resolve()?壓入調(diào)用棧執(zhí)行,?但是promise.then?回調(diào)放入?microtask?隊(duì)列咧织,所以瀏覽器會(huì)先執(zhí)行?console.log(‘end’),打印出?end籍救;

??4.?執(zhí)行完同步事件開始執(zhí)行微任務(wù)习绢,也就是promise1,?promise2蝙昙。解釋:?調(diào)用棧中的代碼被執(zhí)行完成毯炮,回顧?macrotask?的定義,我們知道全局代碼屬于?macrotask耸黑,macrotask?執(zhí)行完桃煎,那接下來就是執(zhí)行?microtask?隊(duì)列的任務(wù)了,執(zhí)行?promise?回調(diào)打印?promise1大刊;promise?回調(diào)函數(shù)默認(rèn)返回?undefined为迈,promise?狀態(tài)變?yōu)?fullfill?觸發(fā)接下來的?then?回調(diào),繼續(xù)壓入?microtask?隊(duì)列缺菌,event?loop?會(huì)把當(dāng)前的?microtask?隊(duì)列一直執(zhí)行完葫辐,此時(shí)執(zhí)行第二個(gè)?promise.then?回調(diào)打印出?promise2;

??5.?這時(shí)?microtask?隊(duì)列已經(jīng)為空伴郁,從上面的流程圖可以知道耿战,接下來主線程會(huì)去做一些?UI?渲染工作(不一定會(huì)做),然后開始下一輪?event?loop焊傅,執(zhí)行?setTimeout?的回調(diào)剂陡,打印出?setTimeout;根據(jù)執(zhí)行時(shí)間和執(zhí)行順序先后setTimeout,setTimeout2狐胎。

??6.?這個(gè)過程會(huì)不斷重復(fù)鸭栖,也就是所謂的事件循環(huán)。

*?視圖渲染的時(shí)機(jī)

回顧上面的事件循環(huán)示意圖握巢,update?rendering(視圖渲染)發(fā)生在本輪事件循環(huán)的?microtask?隊(duì)列被執(zhí)行完之后晕鹊,也就是說執(zhí)行任務(wù)的耗時(shí)會(huì)影響視圖渲染的時(shí)機(jī)。通常瀏覽器以每秒?60?幀(60fps)的速率刷新頁面,據(jù)說這個(gè)幀率最適合人眼交互溅话,大概?16.7ms?渲染一幀晓锻,所以如果要讓用戶覺得順暢,單個(gè)?macrotask?及它相關(guān)的所有?microtask?最好能在?16.7ms?內(nèi)完成飞几。

但也不是每輪事件循環(huán)都會(huì)執(zhí)行視圖更新带射,瀏覽器有自己的優(yōu)化策略,例如把幾次的視圖更新累積到一起重繪循狰,重繪之前會(huì)通知?requestAnimationFrame?執(zhí)行回調(diào)函數(shù)窟社,也就是說?requestAnimationFrame?回調(diào)的執(zhí)行時(shí)機(jī)是在一次或多次事件循環(huán)的?UI?render?階段。

以下代碼可以驗(yàn)證

????setTimeout(function()?{console.log('timer1')},?0)

????requestAnimationFrame(function(){

????console.log('requestAnimationFrame')

????})

????setTimeout(function()?{console.log('timer2')},?0)

????new?Promise(function?executor(resolve)?{

????console.log('promise?1')

????resolve()

????console.log('promise?2')

????}).then(function()?{

????console.log('promise?then')

????})

????console.log('end')

??*?運(yùn)行結(jié)果截圖如下

????1.?運(yùn)行結(jié)果?1:

??????promise?1???????VM88:10?

??????promise?2???????VM88:12?

??????end?????????????VM88:17?

??????promise?then????VM88:14

??????undefined

??????requestAnimationFrame??VM88:4?

??????timer1?????????????????VM88:1?

??????timer2?????????????????VM88:7?

????2.?運(yùn)行結(jié)果?2?:(還沒試出來)

??????promise?1???????

??????promise?2???????

??????end?????????????

??????promise?then????

??????undefined?//

??????timer1?????????????????

??????timer2?????????????????

??????requestAnimationFrame??

??*?可以看到绪钥,結(jié)果?1?中?requestAnimationFrame()是在一次事件循環(huán)后執(zhí)行灿里,而在結(jié)果?2,它的執(zhí)行則是在三次事件循環(huán)結(jié)束后程腹。

*?總結(jié)

??-?事件循環(huán)是?js?實(shí)現(xiàn)異步的核心

??-?每輪事件循環(huán)分為?3?個(gè)步驟:

????a)?執(zhí)行?macrotask?隊(duì)列的一個(gè)任務(wù)

????b)?執(zhí)行完當(dāng)前?microtask?隊(duì)列的所有任務(wù)

????c)?UI?render

??-?瀏覽器只保證?requestAnimationFrame?的回調(diào)在重繪之前執(zhí)行匣吊,沒有確定的時(shí)間,何時(shí)重繪由瀏覽器決定寸潦。

原文:?http://lynnelv.github.io/js-event-loop-browser色鸳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市见转,隨后出現(xiàn)的幾起案子命雀,更是在濱河造成了極大的恐慌,老刑警劉巖斩箫,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吏砂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡乘客,警方通過查閱死者的電腦和手機(jī)狐血,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來易核,“玉大人匈织,你說我怎么就攤上這事∧抵保” “怎么了缀匕?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長井氢。 經(jīng)常有香客問我弦追,道長岳链,這世上最難降的妖魔是什么花竞? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上约急,老公的妹妹穿的比我還像新娘零远。我一直安慰自己,他們只是感情好厌蔽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布牵辣。 她就那樣靜靜地躺著,像睡著了一般奴饮。 火紅的嫁衣襯著肌膚如雪纬向。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天戴卜,我揣著相機(jī)與錄音逾条,去河邊找鬼。 笑死投剥,一個(gè)胖子當(dāng)著我的面吹牛师脂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播江锨,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吃警,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了啄育?” 一聲冷哼從身側(cè)響起酌心,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挑豌,沒想到半個(gè)月后谒府,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浮毯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年完疫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片债蓝。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壳鹤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饰迹,到底是詐尸還是另有隱情芳誓,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布啊鸭,位于F島的核電站锹淌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赠制。R本人自食惡果不足惜赂摆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烟号,春花似錦绊谭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迫筑,卻和暖如春宪赶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脯燃。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工逊朽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人曲伊。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓叽讳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坟募。 傳聞我的和親對(duì)象是個(gè)殘疾皇子岛蚤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345