寫在前面#
入行前端已兩年有餘了。之前我在一篇文章中寫了 —— 要明白前端領域的變與不變。現在和兩年前一樣,各種框架層出不窮,讓人眼花繚亂。很多時候我也在問自己『這個還需要學嗎』。這樣很容易讓我產生焦慮感。而且在面對新事物時,我經常會感到手足無措。歸根結底還是基礎知識不扎實,內功不夠。這也是我寫這篇文章的目的 —— 提升內功,更好的理解『變與不變』。
要開始了#
先從這段代碼開始探索之旅吧
console.log('開始了')
// setTimeout1
setTimeout(function () {
console.log('timeout1')
// promise1
new Promise(function (resolve) {
console.log('timeout1_promise')
resolve()
}).then(function () {
console.log('timeout1_then')
})
}, 2000)
for (var i = 1; i <= 5; i++) {
// setTimeout2
setTimeout(function () {
console.log(i)
}, i * 1000)
console.log(i)
}
// promise2
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('then1')
})
// setTimeout3
setTimeout(function () {
console.log('timeout2')
// promise3
new Promise(function (resolve) {
console.log('timeout2_promise')
resolve()
}).then(function () {
console.log('timeout2_then')
})
}, 1000)
// promise4
new Promise(function (resolve) {
console.log('promise2')
resolve()
}).then(function () {
console.log('then2')
})
開始之前,喜歡思考問題的小夥伴可能會有兩個問題。javascript 為什麼被設計為單線程語言,為什麼又會有同步任務和異步任務的區分。這兩個問題,在這裡我就不細述了。感興趣的同學請看阮老師的這篇文章。
現在我們來看一下 js 大致是怎麼執行的。
到底哪些是宏任務,哪些是微任務呢。大體上這樣區分。
宏任務(macro-task)
- script (整體 JavaScript 代碼)
- setTimeout()
- setInterval()
- setImmediate()
- I/O
- UI render
微任務(micro-task)
- promise
- async/await (同☝)
- process.nextTick
- MutationObserver
預備知識已經準備的差不多了,現在我們開始執行上面那段代碼。
- 首先整體 script 進入主線程,遇到
console.log()
,立即輸出『開始了』 - 接下來遇到
setTimeout
,2s 後回調函數 function () 被分發到宏任務 Event Queue 中(注意這裡不是 2s 後執行),這裡標記為 setTimeout1 - 遇到
for
,直接執行,同理setTimeout
中的回調函數被分發到宏任務 Event Queue 中(這裡涉及到閉包的知識),標記為 setTimeout2,然後執行console.log
,輸出『1,2,3,4,5』 - 遇到
promise
,new Promise
直接執行,輸出『promise1』。then
被分發到微任務 Event Queue 中,標記為 then1 - 接下來又遇到了一個
setTimeout
,1s 後回調函數 function () 被分發到宏任務 Event Queue 中,標記為 setTimeout3 - 又遇到
promise
,同理,輸出『promise2』,then
被分發到微任務 Event Queue 中,標記為 then2
第一輪事件循環的宏任務已經執行完畢。Event Queue 中的任務如下表所示
宏任務 | 微任務 |
---|---|
setTimeout1(2s later) | then1 |
setTimeout2(1s later) | then2 |
setTimeout3(1s later) |
根據上面的流程圖,宏任務執行完後,js 引擎的監視進程會檢查此時有沒有可以執行的微任務。這時後分發到微任務 Event Queue 的then
將被執行。依次輸出『then1』,『then2』
第一輪事件循環全部執行完畢。
好了,現在開始第二輪事件循環 (1s 後)。
- 遇到
setTimeout2
, 輸出『6』,沒有可以執行的微任務。執行新的宏任務。 - 遇到
setTimeout3
, 輸出『timout2』,new promise
立即執行,輸出『timeout2_promise』,then
被分發到微任務 Event Queue 中。標記為 then3
第二輪事件循環的宏任務執行完畢。Event Queue 中的任務如下表所示
宏任務 | 微任務 |
---|---|
setTimeout1(1s later) | then3 |
setTimeout2(1s later) |
同理,宏任務執行完後。執行此輪的微任務 then3。
第二輪事件循環全部執行完畢。
- 遇到
setTimeout1
,執行console.log
,輸出『timeout1』,new promise
立即執行,輸出『timeout1_promise』,then
被分發到微任務 Event Queue 中。標記為 then4 - 第三輪事件循環宏任務執行完畢,執行此輪的微任務
then
,輸出『timeout1_then』
第三輪事件循環執行完畢。
setTimeout2
中依次會產生 4 個宏任務,每隔 1s 輸出一個 6
至此,整段代碼全部執行結束。
總結:宏任務執行完了,執行該宏任務產生的微任務。如果微任務在執行過程中產生新的微任務,則繼續執行微任務。微任務執行完畢後,回到宏任務中開始下一輪循環。