JavaScript 語言特點
- 單線程,而這個線程中擁有唯一的一個事件循環。(web worker 這裡不參與讨論)
- 代碼的執行過程中,除了依靠函數調用棧來搞定函數的執行順序外,還依靠任務隊列(task queue)來搞定另外一些代碼的執行。
- 一個線程中,事件循環是唯一的,但是任務隊列可以擁有多個。
- 任務隊列又分為 macro-task(宏任務)與 micro-task(微任務),在最新标準中,它們被分别稱為 task 與 jobs 。
- setTimeout/Promise 等我們稱之為任務源。而進入任務隊列的是他們指定的具體執行任務。
// setTimeout 中的回調函數才是進入任務隊列的任務
setTimeout(function() {
console.log('xxxx');
})
- 來自不同任務源的任務會進入到不同的任務隊列。其中 setTimeout 與 setInterval 是同源的。
- 事件循環的順序,決定了 JavaScript 代碼的執行順序。它從 script (整體代碼)開始第一次循環。之後全局上下文進入函數調用棧。直到調用棧清空(隻剩全局),然後執行所有的 micro-task 。當所有可執行的 micro-task 執行完畢之後。循環再次從 macro-task 開始,找到其中一個任務隊列執行完畢,然後再執行所有的 micro-task,這樣一直循環下去。
- 其中每一個任務的執行,無論是 macro-task 還是 micro-task,都是借助函數調用棧來完成。
macrotask 與 microtask 類别具體分類
// macrotasks
script(整體代碼), setImmediate, setTimeout, setInterval, I/O, UI rendering
// microtasks
process.nextTick, Promises, Object.observe, MutationObserver
DEMO
setImmediate(function () {
console.log();
}, );
setTimeout(function () {
console.log();
}, );
new Promise(function (resolve) {
console.log();
resolve();
console.log();
}).then(function () {
console.log();
});
console.log();
process.nextTick(function () {
console.log();
});
console.log();
// 3 4 6 8 7 5 1 2
執行過程如下:
- JavaScript引擎首先會從macrotask queue中取出第一個任務,即 script (整段代碼)
- 執行完畢後,将microtask queue中的所有任務取出,按順序全部執;
-
然後再從macrotask queue中取下一個,
執行完畢後,
-
再次将microtask queue中的全部取出;
循環往複,直到兩個queue中的任務都取完。
解釋:
- 代碼開始執行時,所有這些代碼在
中,取出來執行之。macrotask queue
- 後面遇到了
,又加入到setTimeout
中,macrotask queue
- 然後,遇到了
,放入到了另一個隊列promise.then
。microtask queue
- 整個
執行完後,execution context stack
- 取
中的任務了。microtask queue
是以
promise.then
的回調比
setTimeout
先執行。
參考
Event loops
question
Q:
process.nextTick
也會放入
microtask quque
,為什麼優先級比
promise.then
高呢?
A:process.nextTick 永遠大于 promise.then
在 Node 中,_tickCallback 在每一次執行完 TaskQueue 中的一個任務後被調用,而這個 _tickCallback 中實質上幹了兩件事:
1. nextTickQueue中所有任務執行掉(長度最大1e4,Node版本v6.9.1)
2. 第一步執行完後執行
_runMicrotasks
函數,執行
microtask
中的部分(
promise.then
注冊的回調)是以很明顯
process.nextTick > promise.then
”