系列文章目錄
文章目錄
- 系列文章目錄
-
- 一、什麼是事件循環
- 二、宏任務與微任務
- 三、浏覽器事件循環與Nodejs事件循環的差別
-
- 3.1 浏覽器的EventLoop
- 3.2Nodejs的Event Loop
一、什麼是事件循環
js是單線程的,執行js代碼時,遇到同步任務,直接推入調用棧中執行,遇到異步任務時,将任務挂起,等到異步任務有傳回之後推入到任務隊列中,當所有同步任務都執行完後開始将異步任務隊列一個一個推入到主線程中, 異步任務又分為宏任務和微任務
。
上圖要表達的内容用文字來表述的話:
• 同步和異步任務分别進入不同的執行"場所",
同步的進入主線程,異步的進入Event Table并注冊函數。
• 當同步任務的事情完成時
,Event Table會将這個函數移入Event Queue。
• 主線程内的任務執行完畢為空,會去Event Queue讀取對應的函數,進入主線程執行。
上述過程會
不斷重複
,也就是常說的
Event Loop(事件循環)
。
二、宏任務與微任務
宏任務與微任務都表示異步耗時任務
宏任務: dom事件回調、ajax回調、定時器回調、script(整體代碼)、setTimeout、setInterval、(Node的宏任務還有【setImmediate,I/O、UI互動事件】))
微任務: Promise.then catch finally(注意不是說 Promise,new promise直接執行),process.nextTick(Node),MutationObserver(監聽DOM樹的變化 )、Object.observe(⽤來實時監測js中對象的變化)
三、浏覽器事件循環與Nodejs事件循環的差別
浏覽器和Node 環境下,microtask 任務隊列的執行時機不同
- Node端,
microtask 在事件循環的各個階段之間執行
- 浏覽器端,
microtask(微任務) 在事件循環的 macrotask (宏任務)執行完之後執行
3.1 浏覽器的EventLoop
-
,這些任務有的是同步有的是異步(宏任務或微任務),js引擎将所有代碼放入執行棧,并依次彈出并執行
。先執行完同步任務才會執行異步任務
- 如果在執行棧中代碼時發現宏任務則交個浏覽器相應的線程去處理,浏覽器線程在正确的時機(比如定時器最短延遲時間)将宏任務的消息(或稱之為回調函數)推入宏任務隊列。而宏任務隊列中的任務隻有執行棧為空時才會執行。
- 如果執行棧中的代碼時發現微任務則推入微任務隊列,和宏任務隊列一樣,微任務隊列的任務也在執行棧為空時才會執行,
。但是微任務始終比宏任務先執行
- 當執行棧為空時,eventLoop轉到微任務隊列處,依次彈出首個任務放入執行棧并執行,如果在執行的過程中又有微任務産生則推入隊列末尾,這樣循環直到微任務隊列為空。
- 當執行棧和微任務隊列都為空時,eventLoop轉到宏任務隊列,并取出隊首的任務放入執行棧執行。
。需要注意的是宏任務每次循環隻執行一個
- 重複1-5過程
- 直到棧和隊列都為空時,代碼執行結束。引擎休眠等待直至下次任務出現
但是,浏覽器的循環機制有個問題,就是沒有優先級的概念,隻是按照先後順序來執行,那如果有高優先級的任務就得不到及時的執行了。
Node.js解決這些問題,它設計出來的事件循環更細緻一些。
3.2Nodejs的Event Loop
Node.js 是一個新的 JS 運作環境,它同樣要支援異步邏輯,包括定時器、IO、網絡請求,很明顯,也可以用 Event Loop 那一套來跑。
浏覽器的 Event Loop 隻分了兩層優先級,一層是宏任務,一層是微任務。但宏任務之間沒有再劃分優先級,微任務之間也沒有再劃分優先級。
而 Node.js 任務宏任務之間是有優先級的,比如定時器 Timer 的邏輯就比 IO 的邏輯優先級高,因為涉及到時間,越早越準确;而 close 資源的處理邏輯優先級就很低,因為不 close 最多多占點記憶體等資源,影響不大。
于是就把宏任務隊列拆成了五個優先級:Timers、Pending、Poll、Check、Close。
Timers Callback:涉及到時間,肯定越早執行越準确,是以這個優先級最高很容易了解。
Pending Callback:處理網絡、IO 等異常時的回調,有的 *niux 系統會等待發生錯誤的上報,是以得處理下。
Poll Callback:處理 IO 的 data,網絡的 connection,伺服器主要處理的就是這個。
Check Callback:執行setImmediate 的回調,特點是剛執行完 IO 之後就能回調這個。
Close Callback:關閉資源的回調,晚點執行影響也不到,優先級最低。
注意:
Node.js 的 Event Loop 并不是浏覽器那種一次執行一個宏任務,然後執行所有的微任務,而是執行完一定數量的 Timers 宏任務,再去執行所有微任務,然後再執行一定數量的 Pending 的宏任務,然後再去執行所有微任務,剩餘的 Poll、Check、Close 的宏任務也是這樣,即有優先級的宏任務
總結一下: Node.js 對宏任務做了優先級劃分,從高到低分别是 Timers、Pending、Poll、Check、Close 這 5 種,也對微任務做了劃分,也就是 nextTick 的微任務和其他微任務。執行流程是先執行完目前優先級的一定數量的宏任務(剩下的留到下次循環),然後執行 process.nextTick 的微任務,再執行普通微任務,之後再執行下個優先級的一定數量的宏任務。。這樣不斷循環。其中還有一個 Idle/Prepare 階段是給 Node.js 内部邏輯用的,不需要關心。
例題1
console.log('begin');
//then catch finally
process.nextTick(() => {
console.log('nextTick');
});
Promise.resolve().then(() => {
console.log('promise');
})
fs.readFile('./1-複習.txt', (err, data) => {
console.log("檔案讀取成功");
}); // i/o
axios.get('http://121.199.0.35:8888/user/login').then(res => {
console.log(res.status);
}); // set fs axios
setTimeout(() => {
console.log('setTimeout')
}, 0);
(async function() {
console.log('async')
})();
console.log('end');
//輸出結果 begin async end nextTick promise settimeout 檔案讀取成功 200
例題2
console.log('1');
setTimeout(function () {
console.log('2');
process.nextTick(function () {
console.log('3');
})
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
process.nextTick(function () {
console.log('6');
});
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8')
})
setTimeout(function () {
console.log('9');
process.nextTick(function () {
console.log('10');
});
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12')
})
});
// 1 7 6 8 2 4 3 5 9 11 10 12
參考文章:https://zhuanlan.zhihu.com/p/455906298