事件循環與消息隊列
因為js是單線程腳本語言,一般情況下代碼是同步執行。也就是說js執行代碼是一行一行向下執行的,前面沒有執行完成是不會執行后面的代碼的。
- 同步和異步的區別其實就在於需不需要排隊的問題
- 同步:所有任務一視同仁,都得排隊,先來后到;
- 異步:可以按照一定規則(不至於亂套)插隊執行;
- 事件循環和消息隊列怎么理解
- 事件循環:單線程腳本語言javascript處理任務的一種執行機制,通過循環來執行任務隊列里的任務。這個執行過程形象的稱之為事件循環
- 消息隊列:js為單線程腳本語言,執行任務時需要排隊,每當有新的任務來臨時就加到這個隊列后面。這個隊列就叫消息隊列或者任務隊列
瀏覽器與Node的事件循環有何區別?
- 瀏覽器事件循環過程
當某個宏任務執行完后,會查看是否有微任務隊列。
如果有,先執行微任務隊列中的所有任務,
如果沒有,會讀取宏任務隊列中排在最前的任務,執行宏任務的過程中,遇到微任務,依次加入微任務隊列。
棧空后,再次讀取微任務隊列里的任務,依次類推。
- node事件循環過程
1.外部輸入數據
2.輪詢階段(poll)
3.檢查階段(check)
3.關閉事件回調階段(close callback)
4.定時器檢測階段(timer)
5.I/O事件回調階段(I/O callbacks)
6.閑置階段(idle, prepare)
7.輪詢階段(按照該順序反復運行)...
timers 階段:這個階段執行timer(setTimeout、setInterval)的回調
I/O callbacks 階段:處理一些上一輪循環中的少數未執行的 I/O 回調
idle, prepare 階段:僅node內部使用
poll 階段:獲取新的I/O事件, 適當的條件下node將阻塞在這里
check 階段:執行 setImmediate() 的回調
close callbacks 階段:執行 socket 的 close 事件回調
注意:
上面六個階段都不包括 process.nextTick(),這個函數其實是獨立於事件循環之外的,它有一個自己的隊列,當每個階段完成后,
如果存在 nextTick 隊列,就會清空隊列中的所有回調函數,並且優先於其他微任務執行
微任務和宏任務
在js中,任務可以分為同步任務和異步任務,也可以分為微任務和宏任務。同步任務屬於宏任務,有了這些划分,就可以保證所有任務都有條不紊的執行下去,總的來說就是給要執行的任務定了執行規則、划分了優先級。
在總結宏任務與微任務時,我們先要知道我們哪些情況下可能會執行異步操作(未來某個時間執行任務);然后要知道宏任務與微任務是怎么區分的,哪些屬於宏任務,哪些屬於微任務;最后我們要知道宏任務與微任務是通過什么規則來配合執行的。
-
可能存在異步執行的情況
- 回調函數 callback
- Promise/async await
- Generator 函數
- 事件監聽
- 發布/訂閱
- 計時器
- requestAnimationFrame
- MutationObserver
- process.nextTick
- I/O
-
宏任務:
- 所有的同步任務
- I/O, 比如文件讀寫、數據庫數據讀寫等等
- window.setTimeout
- window.setInterval
- window.setImmediate
- window.requestAnimationFrame
-
微任務:
- Promise.then catch finally
- Generator 函數
- async await 和promise是一樣的,屬於微任務
- MutationObserver
-
注:
- process.nextTick(它指定的任務總是發生在所有異步任務之前),網上幾乎無一例外說這是微任務,可是只要存在這個,process.nextTick就會在所有異步任務執行之前執行
- 事件監聽, 比如addeventlistener。宏任務待驗證
- 發布/訂閱 宏任務待驗證
- 有人說同步任務屬於宏任務,關於這中說法我覺得不太准確,應該說同步任務的執行優先級是高於異步任務
-
任務執行過程
- 所有任務都在主進程上執行,異步任務會經歷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');