您好,登錄后才能下訂單哦!
這篇文章主要介紹javascript瀏覽器中事件循環(huán)機(jī)制的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
拋在前面的問(wèn)題:
單線程如何做到異步
事件循環(huán)的過(guò)程是怎樣的
macrotask 和 microtask 是什么,它們有何區(qū)別
單線程和異步
提到j(luò)s,就會(huì)想到單線程,異步,那么單線程是如何做到異步的呢?概念先行,先要了解下單線程和異步之間的關(guān)系。
js的任務(wù)分為 同步 和 異步 兩種,它們的處理方式也不同,同步任務(wù)是直接在主線程上排隊(duì)執(zhí)行,異步任務(wù)則會(huì)被放到任務(wù)隊(duì)列中,若有多個(gè)任務(wù)(異步任務(wù))則要在任務(wù)隊(duì)列中排隊(duì)等待,任務(wù)隊(duì)列類似一個(gè)緩沖區(qū),任務(wù)下一步會(huì)被移到調(diào)用棧(call stack),然后主線程執(zhí)行調(diào)用棧的任務(wù)。
單線程是指js引擎中負(fù)責(zé)解析執(zhí)行js代碼的線程只有一個(gè)(主線程),即每次只能做一件事,而我們知道一個(gè)ajax請(qǐng)求,主線程在等待它響應(yīng)的同時(shí)是會(huì)去做其它事的,瀏覽器先在事件表注冊(cè)ajax的回調(diào)函數(shù),響應(yīng)回來(lái)后回調(diào)函數(shù)被添加到任務(wù)隊(duì)列中等待執(zhí)行,不會(huì)造成線程阻塞,所以說(shuō)js處理ajax請(qǐng)求的方式是異步的。
總而言之,檢查調(diào)用棧是否為空,以及確定把哪個(gè)task加入調(diào)用棧的這個(gè)過(guò)程就是事件循環(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ì)從棧中彈出。
下圖就是調(diào)用棧和任務(wù)隊(duì)列的關(guān)系圖
事件循環(huán)
關(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ì)列(macrotask),每個(gè)外任務(wù)都有自己的分組,瀏覽器會(huì)為不同的任務(wù)組設(shè)置優(yōu)先級(jí)。
macrotask & microtask
規(guī)范有提到兩個(gè)概念,但沒(méi)有詳細(xì)介紹,查閱一些資料大概可總結(jié)如下:
macrotask:包含執(zhí)行整體的js代碼,事件回調(diào),XHR回調(diào),定時(shí)器(setTimeout/setInterval/setImmediate),IO操作,UI render
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ò)程
關(guān)于macrotask和microtask的理解,光這樣看會(huì)有些晦澀難懂,結(jié)合事件循壞的機(jī)制理解清晰很多,下面這張圖可以說(shuō)是介紹得非常清楚了。
總結(jié)起來(lái),一次事件循環(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í)行順序
讀完這么多干巴巴的概念介紹,還不如看一段代碼感受下
console.log('start') setTimeout(function() { console.log('setTimeout') }, 0) Promise.resolve().then(function() { console.log('promise1') }).then(function() { console.log('promise2') }) console.log('end')
打印臺(tái)輸出的log順序是什么?結(jié)合上述的步驟分析,系不系so easy~
首先,全局代碼(main())壓入調(diào)用棧執(zhí)行,打印start;
接下來(lái)setTimeout壓入macrotask隊(duì)列,promise.then回調(diào)放入microtask隊(duì)列,最后執(zhí)行console.log(‘end’),打印出end;
至此,調(diào)用棧中的代碼被執(zhí)行完成,回顧macrotask的定義,我們知道全局代碼屬于macrotask,macrotask執(zhí)行完,那接下來(lái)就是執(zhí)行microtask隊(duì)列的任務(wù)了,執(zhí)行promise回調(diào)打印promise1;
promise回調(diào)函數(shù)默認(rèn)返回undefined,promise狀態(tài)變?yōu)閒ullfill觸發(fā)接下來(lái)的then回調(diào),繼續(xù)壓入microtask隊(duì)列,event loop會(huì)把當(dāng)前的microtask隊(duì)列一直執(zhí)行完,此時(shí)執(zhí)行第二個(gè)promise.then回調(diào)打印出promise2;
這時(shí)microtask隊(duì)列已經(jīng)為空,從上面的流程圖可以知道,接下來(lái)主線程會(huì)去做一些UI渲染工作(不一定會(huì)做),然后開(kāi)始下一輪event loop,執(zhí)行setTimeout的回調(diào),打印出setTimeout;
這個(gè)過(guò)程會(huì)不斷重復(fù),也就是所謂的事件循環(huán)。
視圖渲染的時(shí)機(jī)
回顧上面的事件循環(huán)示意圖,update rendering(視圖渲染)發(fā)生在本輪事件循環(huán)的microtask隊(duì)列被執(zhí)行完之后,也就是說(shuō)執(zhí)行任務(wù)的耗時(shí)會(huì)影響視圖渲染的時(shí)機(jī)。通常瀏覽器以每秒60幀(60fps)的速率刷新頁(yè)面,據(jù)說(shuō)這個(gè)幀率最適合人眼交互,大概16.7ms渲染一幀,所以如果要讓用戶覺(jué)得順暢,單個(gè)macrotask及它相關(guān)的所有microtask最好能在16.7ms內(nèi)完成。
但也不是每輪事件循環(huán)都會(huì)執(zhí)行視圖更新,瀏覽器有自己的優(yōu)化策略,例如把幾次的視圖更新累積到一起重繪,重繪之前會(huì)通知requestAnimationFrame執(zhí)行回調(diào)函數(shù),也就是說(shuō)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')
可以看到,結(jié)果1中requestAnimationFrame()是在一次事件循環(huán)后執(zhí)行,而在結(jié)果2,它的執(zhí)行則是在三次事件循環(huán)結(jié)束后。
總結(jié)
1、事件循環(huán)是js實(shí)現(xiàn)異步的核心
2、每輪事件循環(huán)分為3個(gè)步驟:
a) 執(zhí)行macrotask隊(duì)列的一個(gè)任務(wù)
b) 執(zhí)行完當(dāng)前microtask隊(duì)列的所有任務(wù)
c) UI render
3、瀏覽器只保證requestAnimationFrame的回調(diào)在重繪之前執(zhí)行,沒(méi)有確定的時(shí)間,何時(shí)重繪由瀏覽器決定
以上是“javascript瀏覽器中事件循環(huán)機(jī)制的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。