1. 宏任務與微任務
- 定義
- 宏任務:就是用戶的一些操作的回調(diào),如鼠標點擊事件,鍵盤事件,ajax請求,dom操作等
- 微任務:js引擎的操作的回調(diào),promise,mutationObserver等
- 宏任務與微任務分別包括哪些事件
macrotasks: script,setTimeout, setInterval, setImmediate(Node), I/O(Node), UI rendering
microtasks: process.nextTick(Node), Promises, MutationObserver
2.事件循環(huán)(Event Loop)

image.png
- 當一個宏任務進入執(zhí)行棧中的時候,會先判斷是同步任務還是異步任務,如果是同步任務,就放入主線程中立即執(zhí)行,異步任務的話就先放入事件隊列中排隊等待。
- 當同步任務執(zhí)行完畢后,就會被彈出執(zhí)行棧,然后會先從事件隊列中按照
先進先出的規(guī)則依次從事件隊列中取出宏任務內(nèi)包含的微任務執(zhí)行。 - 當
其內(nèi)的微任務也全部執(zhí)行完畢,然后取出事件隊列中的下一個宏任務入棧繼續(xù)執(zhí)行,直到事件隊列為空則停止循環(huán)。
下面我們做一個簡單的練習:
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');
打印結(jié)果: script start、script end、promise1、promise2、setTimeout
3. 難度升級
相信大家已經(jīng)理解了基礎(chǔ)的事件循環(huán)了,那接下來增加一點難度,讓我們看看下面這段代碼,我可以先告訴大家,當使用不同的方式觸發(fā)它時,我們得到的結(jié)果是不一樣的。
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
new MutationObserver(function () {
console.log('mutate');
}).observe(outer, {
attributes: true,
});
function onClick() {
console.log('click');
setTimeout(function () {
console.log('timeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
當我們通過鼠標點擊觸發(fā)時,
它的打印結(jié)果是: click、 promise、mutate、click、promise、mutate、timeout、timeout
但是,當我們再在這段代碼中加一行:
inner.click()
這段代碼輸出的順序就變成了:click, click,promise,mutate,promise,timeout,timeout
這是為什么呢?
因為:
- 當執(zhí)行到
inner.click時,因為是js調(diào)用的所以屬于script中的同步任務。因此當onClick任務入棧的時候script任務并沒有出棧,此時執(zhí)行棧中有兩個任務script和onClick。 - 執(zhí)行onClick輸出
click,把setTimeout放入宏任務隊列,promise和setAttribute的執(zhí)行監(jiān)聽mutation放入微任務隊列。onClick任務執(zhí)行完畢,出棧。 - 本應該執(zhí)行onClick中的微任務隊列的,但是此時執(zhí)行棧中的
script任務并沒有執(zhí)行完成,所以并沒有執(zhí)行出棧操作,所以微任務隊列繼續(xù)排隊。當執(zhí)行棧為空時會立即執(zhí)行微任務,但是當執(zhí)行棧不為空時,微任務就會繼續(xù)排隊。 - 因為事件冒泡機制,所以
outer的click事件被觸發(fā)入棧,繼續(xù)上一步執(zhí)行順序,但當執(zhí)行到setAttribute時,因為當一個微任務隊列中有MutationObserver時將不會再重復放入隊列中相同的,所以不再進入微任務隊列 -
onClick全部執(zhí)行完畢出棧,script的任務執(zhí)行完畢出棧。執(zhí)行棧為空,開始讓微任務隊列進入執(zhí)行微任務,依次輸出promise, mutate, promise后微任務全部執(zhí)行完畢出棧后,執(zhí)行宏任務隊列中的宏任務setTimeout。
4. 一些補充
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve(console.log('222222')); // 這個不是異步
setTimeout(() => {
console.log(2);
})
reject('error');
})
promise.then((e) => {
console.log(3);
}).then(() => {
console.log(5)
}).catch(e => console.log(e))
console.log(4);
打印結(jié)果

image.png
聲明:我覺得這篇文章寫的特別詳細,只是想自己寫一遍鞏固一下。感謝作者。《Tasks, microtasks, queues and schedules》