徹底了解js事件循環(huán)怎么處理宏任務、微任務
關注 入坑互聯(lián)網 ,回復“加群”
加入我們一起學習,天天進步
Event Loop,事件循環(huán),線程進程。這些概念對初識前端的同學來說可能會一頭霧水。而且運行js代碼的運行環(huán)境除了瀏覽器還有node。因此不同環(huán)境處理Event Loop又變得不同,十分容易混淆。
首先看一段代碼:
console.log('script start');setTimeout(function() {console.log('setTimeout');}, 0);Promise.resolve().then(function() {console.log('promise1');}).then(function() {console.log('promise2');});console.log('script end');
打印順序是什么?
正確答案是
script start, script end, promise1, promise2, setTimeout
為什么會出現(xiàn)這樣打印順序呢?
要理解這些你首先需要對事件循環(huán)機制處理宏任務和微任務的方式有了解。
每個線程都會有它自己的event loop(事件循環(huán)),所以都能獨立運行。然而所有同源窗口會共享一個event loop以同步通信。event loop會一直運行,來執(zhí)行進入隊列的宏任務。一個event loop有多種的宏任務源(譯者注:event等等),這些宏任務源保證了在本任務源內的順序。但是瀏覽器每次都會選擇一個源中的一個宏任務去執(zhí)行。這保證了瀏覽器給與一些宏任務(如用戶輸入)以更高的優(yōu)先級。
宏任務(task)
瀏覽器為了能夠使得JS內部task與DOM任務能夠有序的執(zhí)行,會在一個task執(zhí)行結束后,在下一個 task 執(zhí)行開始前,對頁面進行重新渲染 (task->渲染->task->...)
鼠標點擊會觸發(fā)一個事件回調,需要執(zhí)行一個宏任務,然后解析HTMl。還有下面這個setTimeout,setTimeout的作用是等待給定的時間后為它的回調產生一個新的宏任務。這就是為什么打印‘setTimeout’在‘script end’之后。因為打印‘script end’是第一個宏任務里面的事情,而‘setTimeout’是另一個獨立的任務里面打印的。
微任務(Microtasks )
微任務通常來說就是需要在當前 task 執(zhí)行結束后立即執(zhí)行的任務,比如對一系列動作做出反饋,或者是需要異步的執(zhí)行任務而又不需要分配一個新的 task,這樣便可以減小一點性能的開銷。只要執(zhí)行棧中沒有其他的js代碼正在執(zhí)行且每個宏任務執(zhí)行完,微任務隊列會立即執(zhí)行。如果在微任務執(zhí)行期間微任務隊列加入了新的微任務,會將新的微任務加入隊列尾部,之后也會被執(zhí)行。微任務包括了mutation observe的回調還有接下來的例子promise的回調。
一旦一個pormise有了結果,或者早已有了結果(有了結果是指這個promise到了fulfilled或rejected狀態(tài)),他就會為它的回調產生一個微任務,這就保證了回調異步的執(zhí)行即使這個promise早已有了結果。所以對一個已經有了結果的promise調用.then(yey, nay)會立即產生一個微任務。這就是為什么‘promise1’,'promise2'會打印在‘script end’之后,因為所有微任務執(zhí)行的時候,當前執(zhí)行棧的代碼必須已經執(zhí)行完畢。‘promise1’,'promise2'會打印在‘setTimeout’之前是因為所有微任務總會在下一個宏任務之前全部執(zhí)行完畢。
如何分辨宏任務和微任務?
現(xiàn)在先對前面那個例子進行解釋:
console.log('script start');setTimeout(function() {console.log('setTimeout');}, 0);Promise.resolve().then(function() {console.log('promise1');}).then(function() {console.log('promise2');});console.log('script end');
JS中分為兩種任務類型:macrotask和microtask,在ECMAScript中,microtask稱為jobs,macrotask可稱為task
macrotask(又稱之為宏任務)
1、每一個task會從頭到尾將這個任務執(zhí)行完畢,不會執(zhí)行其它
2、瀏覽器為了能夠使得JS內部task與DOM任務能夠有序的執(zhí)行,會在一個task執(zhí)行結束后,在下一個 task 執(zhí)行開始前,對頁面進行重新渲染 (task->渲染->task->...)
microtask(又稱為微任務),可以理解是在當前task 執(zhí)行結束后立即執(zhí)行的任務
1、也就是說,在當前task任務后,下一個task之前,在渲染之前
2、它的響應速度相比setTimeout(setTimeout是task)會更快,因為無需等渲染
3、在某一個macrotask執(zhí)行完后,就會將在它執(zhí)行期間產生的所有microtask都執(zhí)行完畢(在渲染前)
我們分步驟來進行這個例子過程解答
js執(zhí)行引擎開始執(zhí)行上述代碼時,會先進一個main()方法加入執(zhí)行棧。console.log方法是一個webkit內核支持的普通方法,而不是WebAPIs涉及的方法,所以這里當遇到console.log立即出棧被引擎執(zhí)行。
引擎繼續(xù)往下,將setTimeout添加到執(zhí)行棧。setTimeout()方法屬于事件循環(huán)模型中WebAPIs中的方法,引擎在將setTimeout()方法出棧執(zhí)行時,將延時執(zhí)行的函數(shù)交給了相應模塊,即圖右方的timer模塊來處理。
然后主線程繼續(xù)向下執(zhí)行,遇到promise立即執(zhí)行,then方法存入微任務隊列,promise執(zhí)行之后,即狀態(tài)變更之后,執(zhí)行微任務中的then方法。
最后執(zhí)行setTimeOut;
總結下運行機制:
執(zhí)行一個宏任務(棧中沒有就從事件隊列中獲取)
執(zhí)行過程中如果遇到微任務,就將它添加到微任務的任務隊列中
宏任務執(zhí)行完畢后,立即執(zhí)行當前微任務隊列中的所有微任務(依次執(zhí)行)
當前宏任務執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染
渲染完畢后,JS線程繼續(xù)接管,開始下一個宏任務(從事件隊列中獲取)
?? 看完三件事
如果你覺得這篇內容對你挺有啟發(fā),我想邀請你幫我三個小忙:
點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓)。 關注公眾號「入坑互聯(lián)網」,不定期分享原創(chuàng)知識。 也看看其它文章
- END -
結伴同行前端路

