js 事件迴圈訊息佇列和微任務巨集任務
阿新 • • 發佈:2020-07-10
事件迴圈與訊息佇列
因為js是單執行緒指令碼語言,一般情況下程式碼是同步執行。也就是說js執行程式碼是一行一行向下執行的,前面沒有執行完成是不會執行後面的程式碼的。
- 同步和非同步的區別其實就在於需不需要排隊的問題
- 同步:所有任務一視同仁,都得排隊,先來後到;
- 非同步:可以按照一定規則(不至於亂套)插隊執行;
- 事件迴圈和訊息佇列怎麼理解
- 事件迴圈:單執行緒指令碼語言javascript處理任務的一種執行機制,通過迴圈來執行任務佇列裡的任務。這個執行過程形象的稱之為事件迴圈
- 訊息佇列:js為單執行緒指令碼語言,執行任務時需要排隊,每當有新的任務來臨時就加到這個佇列後面。這個佇列就叫訊息佇列或者任務佇列
微任務和巨集任務
在js中,任務可以分為同步任務和非同步任務,非同步任務又可以細分為微任務和巨集任務。有了這些劃分,就可以保證所有任務都有條不紊的執行下去,總的來說就是給要執行的非同步任務定了執行規則、劃分了優先順序。
在總結巨集任務與微任務時,我們先要知道我們哪些情況下可能會執行非同步操作(未來某個時間執行任務);然後要知道巨集任務與微任務是怎麼區分的,哪些屬於巨集任務,哪些屬於微任務;最後我們要知道巨集任務與微任務是通過什麼規則來配合執行的。
-
可能存在非同步執行的情況
- 回撥函式 callback
- Promise/async await
- Generator 函式
- 事件監聽
- 釋出/訂閱
- 計時器
- requestAnimationFrame
- MutationObserver
- process.nextTick
- I/O
-
巨集任務:
- I/O, 比如檔案讀寫、資料庫資料讀寫等等
- 事件監聽, 比如addeventlistener
- 釋出/訂閱
- window.setTimeout
- window.setInterval
- window.setImmediate
- window.requestAnimationFrame
-
微任務:
- Generator 函式
- Promise.then catch finally
- async await
- process.nextTick(它指定的任務總是發生在所有非同步任務之前)
- MutationObserver
-
任務執行過程
- 所有任務都在主程序上執行,非同步任務會經歷2個階段 Event Table和Event Queue
- 同步任務在主程序排隊執行,非同步任務(包括巨集任務和微任務)在事件佇列排隊等待進入主程序執行
- 遇到巨集任務推進巨集任務佇列,遇到微任務推進微任務佇列(巨集任務佇列的項一般對應一個微任務佇列,有點像一個大哥帶著一群小馬仔,這就組成一組非同步任務。如果有巢狀那就會有多個大哥小馬仔)
- 執行巨集任務,執行完巨集任務,檢查有沒有當前層的微任務(大哥帶著小馬仔逐步亮相。。。)
- 繼續執行下一個巨集任務,然後執行對應層次的微任務,直到全部執行完畢(下一個大哥帶著他的小馬仔亮相。。。)
-
盜圖兩張
- 同步任務與非同步任務執行流程
- 微任務與巨集任務執行流程
- 同步任務與非同步任務執行流程
舉個栗子
下面舉個例子加深印象,例子來自網路。當存在多個不同的非同步操作時,看宿主環境(node、瀏覽器等等)是怎麼執行的,可以把下面程式碼或者練習題的程式碼拷出來,利用瀏覽器斷點看下執行過程。
//因為涉及到process 所以應該在node環境下執行
console.log('1') //主程序 執行
setTimeout(function() {
console.log('2') //因為setTimeout是巨集任務,所以加入巨集任務佇列1,['2']
process.nextTick(function() {
console.log('3') //因為 process.nextTick是微任務,所以加入微任務佇列2,['4','3']
})
new Promise(function(resolve) {
console.log('4') //因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['4']
resolve()
}).then(function() {
console.log('5') // 因為promise then 是微任務,所以推入微任務佇列2,['4','3','5']
})
},0)
// process.nextTick總是發生在所有非同步任務之前
process.nextTick(function() {
console.log('6') //因為process.nextTick是微任務,所以推入微任務佇列1,['6']
new Promise(function(resolve) {
console.log('7')//因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['6','7']
resolve()
}).then(function() {
console.log('8')//因為 promise then 是微任務,所以推入微任務佇列1,['6','7','8']
})
setTimeout(function() {
console.log('9')//因為setTimeout是巨集任務,所以推入巨集任務佇列2 ,['9']
process.nextTick(function() {
console.log('10')//因為process.nextTick是微任務,所以推入微任務佇列3,['9','11','12','10']
})
new Promise(function(resolve) {
console.log('11')//因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['9','11']
resolve()
console.log('12')////因為此處程式碼執行不屬於非同步,所以直接推入主程式執行,['9','11','12']
}).then(function() {
console.log('13')//因為 promise then 是微任務,所以推入微任務佇列3,['9','11','12','10','12']
})
},0)
})
//列印輸出
// 1
// 6
// 7
// 8
// 2
// 4
// 3
// 5
// 9
// 11
// 12
// 10
// 13
得出結論:微任務優先順序大小:process.nextTick > setTimeout
練習題
位元組筆試題
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');