JS事件循環(huán)-微任務-宏任務(原理講解+面試題分析)
前言
JS代碼在運行時,有兩種運行環(huán)境。
一是在瀏覽器中,二是在node中。
由于JS線程是單線程,在運行JS代碼時,可能會遇到比較耗時的操作,比如setTimeout,或者是發(fā)送網(wǎng)絡請求等,又由于JS線程是單線程,如果在解析耗時的代碼時候,停在了這里,那執(zhí)行代碼的性能將是比較低的。
為了解決此問題,在瀏覽器、node環(huán)境下,其實是有事件循環(huán)機制的。
瀏覽器的事件循環(huán)
瀏覽器的事件循環(huán)
JS線程執(zhí)行代碼時候,遇到比較耗時的操作時,將這些操作交給瀏覽器去處理,然后這些操作會根據(jù)不同的種類放進微任務隊列或者宏任務隊列,宏任務隊列和微任務隊列都不為空的時候,只有等微任務隊列為空,即微任務隊列里面的事件全部都執(zhí)行完之后,才會再去讓宏任務隊列中的事件出棧,之后交由JS線程去處理,執(zhí)行代碼。
事件循環(huán)大概就是如圖所示的流程:
瀏覽器的宏任務、微任務
其實,在瀏覽器拿到那些有些不能同步處理的事件的時候,有的會加入宏任務隊列,有的會加入微任務隊列,那么一般我們如何區(qū)分呢?
一般情況下:加入宏任務隊列和微任務隊列的事件如下:
宏任務隊列(macrotask queue):ajax、setTimeout、setInterval、DOM監(jiān)聽、UI Rendering等
微任務隊列(microtask queue):Promise的then回調、 Mutation Observer API、queueMicrotask()。
那么這些事件的執(zhí)行順序是怎么樣子的呢?
首先,有一個原則,宏任務隊列里面的事件,要執(zhí)行的話,一定是在確保微任務隊列為空的情況下,即微任務隊列里面的事件全部執(zhí)行完的情況。
其次,main script里面的內容是最先執(zhí)行的。
由此,可以得到執(zhí)行順序為:main script > 微任務隊列里面的事件 > 宏任務里面的事件。
面試題一
題目如下:
setTimeout(function () { console.log("setTimeout1"); new Promise(function (resolve) { resolve(); }).then(function () { new Promise(function (resolve) { resolve(); }).then(function () { console.log("then4"); }); console.log("then2"); }); }); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("then1"); }); setTimeout(function () { console.log("setTimeout2"); }); console.log(2); queueMicrotask(() => { console.log("queueMicrotask1") }); new Promise(function (resolve) { resolve(); }).then(function () { console.log("then3"); }); // promise1 // 2 // then1 // queueMicrotask1 // then3 // setTimeout1 // then2 // then4 // setTimeout2
分析如下:
在第一個事件循環(huán)里面,main script、宏任務、微任務里面的事件如下:
在判斷加入宏任務隊列還是微任務隊列時候,遵循如下原則:
宏任務隊列(macrotask queue):ajax、setTimeout、setInterval、DOM監(jiān)聽、UI Rendering等
微任務隊列(microtask queue):Promise的then回調、 Mutation Observer
API、queueMicrotask()。
按照這個原則,第一輪事件循環(huán)里面的事件如下:
先執(zhí)行main script、然后微任務隊列里面的,最后是宏任務隊列里面的
// promise1
// 2
// then1
// queueMicrotask1
// then3
之后執(zhí)行setTimeout1的宏任務,此時第二輪事件循環(huán)里面的內容如下:
第二輪事件循環(huán)執(zhí)行內容如下:
// setTimeout1
// then2
// then4
// setTimeout2
綜上:最后執(zhí)行結果為:
// promise1 // 2 // then1 // queueMicrotask1 // then3 // setTimeout1 // then2 // then4 // setTimeout2
面試題二
題目如下:
// async function bar() { // console.log("22222") // return new Promise((resolve) => { // resolve() // }) // } // async function foo() { // console.log("111111") // await bar() // console.log("33333") // } // foo() // console.log("444444") async function async1 () { console.log('async1 start') await async2(); console.log('async1 end') } async function async2 () { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout') }, 0) async1(); new Promise (function (resolve) { console.log('promise1') resolve(); }).then (function () { console.log('promise2') }) console.log('script end') // script start // async1 start // async2 // promise1 // script end // async1 end // promise2 // setTimeout
第一輪事件循環(huán)里面的事件如下:
然后按照順序執(zhí)行,最后結果如下:
// script start // async1 start // async2 // promise1 // script end // async1 end // promise2 // setTimeout
node的事件循環(huán)
node的事件循環(huán)
瀏覽器的事件循環(huán)是是根據(jù)HTML5定義的規(guī)范來實現(xiàn)的,不同的瀏覽器可能會有不同的實現(xiàn),而Node中是由libuv實現(xiàn)的。
首先我們看一下node的架構圖:
我們可以從圖中大致看出,事件循環(huán)是在libuv中實現(xiàn)的,libuv主要維護的是一個事件循環(huán)(Event Loop)和 線程池(worker threads)。
libuv是一個多平臺的專注于異步IO的庫,它最初是為Node開發(fā)的,但是現(xiàn)在也被使用到Luvit、Julia、pyuv等其他地方;
EventLoop負責調用系統(tǒng)的一些其他操作:文件的IO、Network、child-processes等
由圖可以看出,事件循環(huán)就像是一個橋梁,是連接著應用程序的JavaScript(左邊部分)和系統(tǒng)調用(右邊線程池部分)之間的通道:
無論是我們的文件IO、數(shù)據(jù)庫、網(wǎng)絡IO、定時器、子進程,在完成對應的操作后,都會將對應的結果和回調函數(shù)放到事件循環(huán)(任務隊列)中;
事件循環(huán)會不斷的從任務隊列中取出對應的事件(回調函數(shù))來執(zhí)行;
但是一次完整的事件循環(huán)Tick分成很多個階段:
- 定時器(Timers):本階段執(zhí)行已經(jīng)被 setTimeout() 和 setInterval() 的調度回調函數(shù)。
- 待定回調(Pending Callback):對某些系統(tǒng)操作(如TCP錯誤類型)執(zhí)行回調,比如TCP連接時接收到ECONNREFUSED。idle, prepare:僅系統(tǒng)內部使用。
- 輪詢(Poll):檢索新的 I/O 事件;執(zhí)行與 I/O 相關的回調;
- 檢測(check):setImmediate() 回調函數(shù)在這里執(zhí)行。
- 關閉的回調函數(shù):一些關閉的回調函數(shù),如:socket.on(‘close’, …)
node的宏任務、微任務
node中也有微任務和宏任務,執(zhí)行的原則和在瀏覽器中一樣,是先執(zhí)行微任務,然后再執(zhí)行宏任務,但是對于宏任務來說,是按照上圖從上到下的順序執(zhí)行的。
具體對應的常見事件的執(zhí)行順序如下;
在微任務隊列中:
- next tick queue:process.nextTick;
- other queue:Promise的then回調、queueMicrotask;
(是按照從上往下的事件順序執(zhí)行)
在宏任務隊列:
- timer queue:setTimeout、setInterval;
- poll queue:IO事件;
- check queue:setImmediate;
- close queue:close事件
(同樣是按照從上往下的事件順序執(zhí)行)
所以,綜上所述,在每一次事件循環(huán)的tick中,會按照如下順序來執(zhí)行代碼:
next tick microtask queue;
other microtask queue;
timer queue;
poll queue;
check queue;
close queue
當然,main script 依舊是最先執(zhí)行的,只有main script執(zhí)行結束后,才會按照上述順序來執(zhí)行代碼。
面試題一
async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout0') }, 0) setTimeout(function () { console.log('setTimeout2') }, 300) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick1')); async1(); process.nextTick(() => console.log('nextTick2')); new Promise(function (resolve) { console.log('promise1') resolve(); console.log('promise2') }).then(function () { console.log('promise3') }) console.log('script end') // script start // async1 start // async2 // promise1 // promise2 // script end // nexttick1 // nexttick2 // async1 end // promise3 // settimetout0 // setImmediate // setTimeout2
第一輪事件循環(huán)里面的事件如下:
按照順序自左向右執(zhí)行,3s后執(zhí)行setTimeout2,
最后的結果是:
// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2
到此這篇關于JS事件循環(huán)-微任務-宏任務(原理講解+面試題分析)的文章就介紹到這了,更多相關循環(huán)-微任務-宏任務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Js類的靜態(tài)方法與實例方法區(qū)分及jQuery拓展的兩種方法
這篇文章主要介紹了Js類的靜態(tài)方法與實例方法區(qū)分及jQuery拓展的兩種方法 的相關資料,對靜態(tài)方法(Static)和實例方法(非Static)不太理解的朋友可以一起學習下2016-06-06js實現(xiàn)網(wǎng)頁右上角滑出會自動消失大幅廣告的方法
這篇文章主要介紹了js實現(xiàn)網(wǎng)頁右上角滑出會自動消失大幅廣告的方法,是javascript廣告特效的典型應用,非常具有實用價值,需要的朋友可以參考下2015-02-02微信小程序實現(xiàn)動態(tài)顯示和隱藏某個控件功能示例
這篇文章主要介紹了微信小程序實現(xiàn)動態(tài)顯示和隱藏某個控件功能,涉及微信小程序事件響應及樣式動態(tài)操作相關實現(xiàn)技巧,需要的朋友可以參考下2018-12-12Js參數(shù)RSA加密傳輸之jsencrypt.js的使用
這篇文章主要介紹了Js參數(shù)RSA加密傳輸之jsencrypt.js的使用,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02JavaScript遍歷json對象數(shù)據(jù)的方法
這篇文章介紹了JavaScript遍歷json對象數(shù)據(jù)的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04