1. 什么是事件循環(huán)屹逛?
js引擎并不是獨(dú)立運(yùn)行的深啤,它
運(yùn)行在宿主環(huán)境
中拗馒,對多數(shù)開發(fā)者來說通常就是web瀏覽器。經(jīng)過最近幾年的發(fā)展溯街,js已經(jīng)超出了瀏覽器的范圍诱桂,進(jìn)入了其他環(huán)境。
所以這些環(huán)境都有一個(gè)共同“點(diǎn)”(thread,也指線程
),即它們都提供了一種機(jī)制來處理程序中多個(gè)塊的執(zhí)行呈昔,且執(zhí)行每塊時(shí)調(diào)用js引擎
挥等,這種機(jī)制被稱為事件循環(huán)
。
2. event loop它最主要是分三部分:主線程堤尾、宏隊(duì)列(macrotask)肝劲、微隊(duì)列(microtask)
js的任務(wù)隊(duì)列分為同步任務(wù)和異步任務(wù),所有的同步任務(wù)都是在主線程里執(zhí)行的,異步任務(wù)可能會在macrotask或者microtask里面辞槐。
3. 主線程
就是訪問到的script標(biāo)簽里面包含的內(nèi)容掷漱,或者是直接訪問某一個(gè)js文件的時(shí)候,里面的可以在當(dāng)前作用域直接執(zhí)行的所有內(nèi)容(執(zhí)行的方法榄檬、new出來的對象)
4. js引擎的兩大特點(diǎn):單線程和非阻塞
- 單線程:只有一個(gè)主線程來處理任務(wù)卜范。
- 非阻塞:當(dāng)執(zhí)行異步任務(wù)時(shí),不必等到結(jié)果返回鹿榜,主線程會掛起(pending)這個(gè)任務(wù)海雪,然后根據(jù)一定規(guī)則執(zhí)行回調(diào)函數(shù)(事件循環(huán)機(jī)制)。
JS通常是非阻塞的舱殿,除了某些特殊情況奥裸,JS會停止代碼執(zhí)行:
alert, confirm, prompt(除了Opera)。
“頁面上的程序正忙”的系統(tǒng)對話框彈出沪袭。
5. 任務(wù)分類:同步和異步
- 同步任務(wù)(macrotask ):會立即執(zhí)行的任務(wù)刺彩。
- 異步任務(wù)(microtask ):不會立即執(zhí)行的任務(wù),可細(xì)分為一下兩種:宏任務(wù)和微任務(wù)枝恋。
--宏任務(wù):script, setTimeout, setInterval, setImmeditate, T/O, UI rendering
-- 微任務(wù):process, nextTick, promise.then(), object.observe, MutationObserver
6. 執(zhí)行順序
1、先執(zhí)行主線程
2嗡害、遇到宏隊(duì)列(macrotask)放到宏隊(duì)列(macrotask)
3焚碌、遇到微隊(duì)列(microtask)放到微隊(duì)列(microtask)
4、主線程執(zhí)行完畢
5霸妹、執(zhí)行微隊(duì)列(microtask)十电,微隊(duì)列(microtask)執(zhí)行完畢
6、執(zhí)行一次宏隊(duì)列(macrotask)中的一個(gè)任務(wù)叹螟,執(zhí)行完畢
7鹃骂、執(zhí)行微隊(duì)列(microtask),執(zhí)行完畢
8罢绽、依次循環(huán)畏线。。良价。
7. 執(zhí)行棧和任務(wù)隊(duì)列
占內(nèi)存和堆內(nèi)存:
棧內(nèi)存保存著JS的變量和指向堆內(nèi)存中對象的指針寝殴,堆內(nèi)存保存著對象。
- 執(zhí)行棧:
后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)明垢,當(dāng)函數(shù)被調(diào)用時(shí)添加到執(zhí)行棧頂部蚣常,從棧頂移出。當(dāng)一個(gè)任務(wù)為同步任務(wù)時(shí)痊银,則會被立即執(zhí)行抵蚊,執(zhí)行完移出;如果是異步任務(wù),則會交給異步處理模塊處理贞绳,當(dāng)異步任務(wù)回調(diào)達(dá)到觸發(fā)條件時(shí)谷醉,會將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中,如果是宏任務(wù)熔酷,則添加到宏任務(wù)隊(duì)列中孤紧,如果是為微任務(wù),則添加到微任務(wù)隊(duì)列中拒秘。
- 任務(wù)隊(duì)列:
圖示:任務(wù)隊(duì)列讀取任務(wù)的順序?yàn)楹耆蝿?wù)-微任務(wù)(微任務(wù)隊(duì)列全部執(zhí)行結(jié)束才進(jìn)入到下一輪宏任務(wù))-宏任務(wù)-微任務(wù)的順序号显;當(dāng)執(zhí)行棧中的任務(wù)全部完成之后會從事件隊(duì)列中讀取一個(gè)任務(wù)添加到執(zhí)行棧。一個(gè)任務(wù)隊(duì)列中可以有多個(gè)宏任務(wù)隊(duì)列躺酒,但只能有一個(gè)微任務(wù)隊(duì)列押蚤。
- 引入微任務(wù)隊(duì)列后,事實(shí)上事件循環(huán)執(zhí)行的流程是這樣的:
- 一開始把一整段的JS腳本作為第一個(gè)宏任務(wù)執(zhí)行
- 在執(zhí)行過程中羹应,同步代碼則直接運(yùn)行揽碘,過程中存在宏任務(wù)則進(jìn)入到宏任務(wù)隊(duì)列,微任務(wù)在微任務(wù)隊(duì)列中入列园匹。
- 在當(dāng)前宏任務(wù)執(zhí)行完成后雳刺,檢查微任務(wù)隊(duì)列,若存在微任務(wù)則按序全部執(zhí)行完畢裸违。
- 繼續(xù)檢查宏任務(wù)隊(duì)列掖桦,執(zhí)行下一個(gè)宏任務(wù),如此反復(fù)供汛。
理解了原理后枪汪,可以根據(jù)這個(gè)步驟來多看幾個(gè)例子,嘗試寫出打印的結(jié)果怔昨。
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
});
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0);
console.log('start');
// start
// Promise1
// setTimeout1
// Promise2
// setTimeout2
- 有個(gè)小 tip:從規(guī)范來看雀久,microtask (微任務(wù))優(yōu)先于 macrotask(宏任務(wù)) 執(zhí)行,所以如果有需要優(yōu)先執(zhí)行的邏輯趁舀,放入microtask 隊(duì)列會比 task 更早的被執(zhí)行赖捌。