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