前言
以下內容是js在瀏覽器中的事件佇列執行,與在nodejs中有所區別,請注意。
都說js是單執行緒的,不過它本身其實不是單執行緒,但是在瀏覽器中執行時只分配一個執行緒進行執行。
所以說js執行是單執行緒的,一次只能進行一項任務,就是一件任務一件任務做,做完一件做下一件。
認識一個棧兩個佇列
一個呼叫棧Stack。
一個巨集佇列,macrotask,也叫tasks。
一個微佇列,microtask,也叫jobs。
執行過程
js就是執行全域性Script同步程式碼,這中間碰到一些非同步任務先加進對應的佇列。
做完之後,呼叫棧就為空了。
然後將佇列(先微佇列後巨集佇列
)裡面的首個任務提到呼叫棧來做,一件一件做完直到佇列中的任務都做完。
總結就是,先做同步
的任務,再做微佇列
的任務,再做巨集佇列
的任務。
非同步任務怎麼分配
這些非同步任務包括但不限於:
以下分配給巨集佇列
:
setTimeout
setInterval
requestAnimationFrame
I/O
UI rendering
以下分配給微佇列
:
Promise
Object.observe
MutationObserver
常見的巨集佇列:setTimeout
,常見的微佇列:Promise
。
簡單例子
console.log("同步任務1");
setTimeout(() => {
console.log("巨集任務");
});
new Promise((resolve, reject) => {
console.log("同步任務2");
resolve("微任務");
}).then((data) => {
console.log(data);
});
console.log("同步任務3");
結果是(按標號加任務,按箭頭執行):
需要注意的是Promise的第一層沒有執行回撥之前是同步
的,也就是上面的同步任務2
。
難一點的例子
console.log("同步任務1");
console.log("同步任務2");
new Promise((resolve, reject) => {
console.log("同步任務3");
setTimeout(() => {
console.log("巨集任務1");
Promise.resolve()
.then(() => {
console.log("微任務5");
})
.then(() => {
console.log("微任務6");
});
});
resolve("微任務1");
})
.then((data) => {
console.log(data);
return "微任務3";
})
.then((data) => {
console.log(data);
});
setTimeout(() => {
console.log("巨集任務2");
}, 0);
new Promise((resolve, reject) => {
resolve("微任務2");
})
.then((data, resolve) => {
console.log(data);
return "微任務4";
})
.then((data) => {
console.log(data);
});
console.log("同步任務4");
如何看呢,先看第一層,紅色代表同步,綠色微任務,藍色巨集任務。
我們會把同步任務執行完,然後看見微任務有倆,巨集任務也有倆。
本來的執行順序可能是這樣(我這裡按照序號來表達順序了,請和簡單例子區分開來):
但是沒那麼順利,執行到標號6
時不一樣了。
因為微任務執行過程中可能會產生新的微任務
。
上面的微任務1
執行完會把微任務3
加在微任務2
後面,也就是微任務2
執行完也輪不到巨集任務,會繼續執行新的微任務直到微任務佇列暫時為空。
所以接下來會按照加入佇列的順序執行完四個微任務,這時候發現沒有新的微任務產生,才開始執行巨集任務:
但是需要注意的是,上面執行到標號5
時又不一樣了,巨集任務一執行後又產生了新的微任務,所以巨集任務兩個並沒有順利連續執行,而是被插入的微任務攔住了。
(要記住微任務與巨集任務佇列都存在時一定是微任務先執行完再來執行巨集任務,即使是巨集任務執行產生的微任務也同理
)
所以最後的答案,如果存在不理解的,可以在認真回顧一下上文: