事件循環(huán)與宏任務、微任務

1. 宏任務與微任務

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

2.事件循環(huán)(Event Loop)

image.png
  1. 當一個宏任務進入執(zhí)行棧中的時候,會先判斷是同步任務還是異步任務,如果是同步任務,就放入主線程中立即執(zhí)行,異步任務的話就先放入事件隊列中排隊等待。
  2. 當同步任務執(zhí)行完畢后,就會被彈出執(zhí)行棧,然后會先從事件隊列中按照先進先出的規(guī)則依次從事件隊列中取出宏任務內(nèi)包含的微任務執(zhí)行。
  3. 其內(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、mutateclick、promisemutate、timeout、timeout

但是,當我們再在這段代碼中加一行:

inner.click()

這段代碼輸出的順序就變成了:click, click,promise,mutate,promise,timeout,timeout

這是為什么呢?

因為:

  1. 當執(zhí)行到inner.click時,因為是js調(diào)用的所以屬于script中的同步任務。因此當onClick任務入棧的時候script任務并沒有出棧,此時執(zhí)行棧中有兩個任務scriptonClick。
  2. 執(zhí)行onClick輸出click,把setTimeout放入宏任務隊列,promise和setAttribute的執(zhí)行監(jiān)聽mutation放入微任務隊列。onClick任務執(zhí)行完畢,出棧。
  3. 本應該執(zhí)行onClick中的微任務隊列的,但是此時執(zhí)行棧中的script任務并沒有執(zhí)行完成,所以并沒有執(zhí)行出棧操作,所以微任務隊列繼續(xù)排隊。當執(zhí)行棧為空時會立即執(zhí)行微任務,但是當執(zhí)行棧不為空時,微任務就會繼續(xù)排隊。
  4. 因為事件冒泡機制,所以outer的click事件被觸發(fā)入棧,繼續(xù)上一步執(zhí)行順序,但當執(zhí)行到setAttribute時,因為當一個微任務隊列中有MutationObserver時將不會再重復放入隊列中相同的,所以不再進入微任務隊列
  5. 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》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容