js 是單線程執(zhí)行的,js中的任務(wù)按順序一個一個的執(zhí)行,但是一個任務(wù)耗時太長,那么后面的任務(wù)就需要等待,為了解決這種情況,將任務(wù)分為了同步任務(wù)和異步任務(wù),而異步任務(wù)又可以分為微任務(wù)和宏任務(wù)。
首先第一段示例代碼
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
打印順序為:
script start
script end
promise1
promise
setTimeout
具體為什么會打印出這個順序,下面在具體解釋。
我們具體看一下js的執(zhí)行流程:
張倩qianniuerlv-2 JS事件循環(huán)機制(event loop)之宏任務(wù)/微任務(wù)解讀:
- 同步和異步任務(wù)分別進入不同的執(zhí)行"場所",同步的進入主線程,異步的進入Event Table并注冊函數(shù)
- 當指定的事情完成時,Event Table會將這個函數(shù)移入Event Queue。
- 主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會去Event Queue讀取對應(yīng)的函數(shù),進入主線程執(zhí)行。
- 上述過程會不斷重復(fù),也就是常說的Event Loop(事件循環(huán))。
在js引擎中,存在一個叫monitoring process的進程,這個進程會不斷的檢查主線程的執(zhí)行情況,一旦為空,就會去Event Quene檢查有哪些待執(zhí)行的函數(shù)。
微任務(wù) 和 宏任務(wù)
微任務(wù)和宏任務(wù)的問題應(yīng)該是前端面試中比較常見的,他們都從屬于異步任務(wù),主要區(qū)別在于他們的執(zhí)行順序,Event Loop的走向和取值
這張圖的意思就是:
- 存在微任務(wù)的話,那么就執(zhí)行所有的微任務(wù)
- 微任務(wù)都執(zhí)行完之后,執(zhí)行第一個宏任務(wù),
- 循環(huán) 1, 2
從參考博主的博客里看到這段,這邊不得不提一句,我也是看了這為博主的博客才理清楚了微任務(wù)和宏任務(wù)的概念。博主的鏈接會在文章末給出。
一個掘金的老哥(ssssyoki)的文章摘要:
那么如此看來我給的答案還是對的。但是js異步有一個機制,就是遇到宏任務(wù),先執(zhí)行宏任務(wù),將宏任務(wù)放入eventqueue,然后在執(zhí)行微任務(wù),將微任務(wù)放入eventqueue最騷的是,這兩個queue不是一個queue。當你往外拿的時候先從微任務(wù)里拿這個回掉函數(shù),然后再從宏任務(wù)的queue上拿宏任務(wù)的回掉函數(shù)。 我當時看到這我就服了還有這種騷操作。
這邊我們可以看出,微任務(wù)和宏任務(wù)是同屬于兩個不同的隊列的!?。?/p>
- 宏任務(wù)一般包括:整體代碼script,setTimeout,setInterval、setImmediate。
- 微任務(wù)一般包括:原生Promise(有些實現(xiàn)的promise將then方法放到了宏任務(wù)中)、process.nextTick、Object.observe(已廢棄)、 MutationObserver
一段喪心病狂的代碼
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')
})
})
第一輪循環(huán):
- 首先打印 1
- 下面是setTimeout是異步任務(wù)且是宏任務(wù),加入宏任務(wù)暫且記為 setTimeout1
- 下面是 process 微任務(wù) 加入微任務(wù)隊列 記為 process1
- 下面是 new Promise 里面直接 resolve(7) 所以打印 7 后面的then是微任務(wù) 記為 then1
- setTimeout 宏任務(wù) 記為 setTimeout2
第一輪循環(huán)打印出的是 1 7
當前宏任務(wù)隊列:setTimeout1, setTimeout2
當前微任務(wù)隊列:process1, then1,
第二輪循環(huán):
- 執(zhí)行所有微任務(wù)
- 執(zhí)行process1,打印出 6
- 執(zhí)行then1 打印出8
- 微任務(wù)都執(zhí)行結(jié)束了,開始執(zhí)行第一個宏任務(wù)
- 執(zhí)行 setTimeout1 也就是 第 3 - 14 行
- 首先打印出 2
- 遇到 process 微任務(wù) 記為 process2
- new Promise中resolve 打印出 4
- then 微任務(wù) 記為 then2
第二輪循環(huán)結(jié)束,當前打印出來的是 1 7 6 8 2 4
當前宏任務(wù)隊列:setTimeout2
當前微任務(wù)隊列:process2, then2
第三輪循環(huán):
- 執(zhí)行所有的微任務(wù)
- 執(zhí)行 process2 打印出 3
- 執(zhí)行 then2 打印出 5
- 執(zhí)行第一個宏任務(wù),也就是執(zhí)行 setTimeout2 對應(yīng)代碼中的 25 - 36 行
- 首先打印出 9
- process 微任務(wù) 記為 process3
- new Promise執(zhí)行resolve 打印出 11
- then 微任務(wù) 記為 then3
當前打印順序為:1 7 6 8 2 4 3 5 9 11
當前宏任務(wù)隊列為空
當前微任務(wù)隊列:process3,then3
第四輪循環(huán):
- 執(zhí)行所有的微任務(wù)
- 執(zhí)行process3 打印出 10
- 執(zhí)行then3 打印出 12
代碼執(zhí)行結(jié)束:
最終打印順序為:1 7 6 8 2 4 3 5 9 11 10 12
(請注意,node環(huán)境下的事件監(jiān)聽依賴libuv與前端環(huán)境不完全相同,輸出順序可能會有誤差)