js中的微任務(wù)與宏任務(wù)

導(dǎo)論

先看題目:

question:試著寫出下面程序的輸出結(jié)果:

  console.log(1);
  
  setTimeout(() => {
    console.log(2);
    new Promise((resolve) => {
      console.log(3);
      resolve();
    }).then(res => {
      console.log(4);
    })
    console.log(5);
  }, 0);
  
  new Promise((resolve) => {
    console.log(6);
    resolve();
  }).then(res => {
    console.log(7);
  })
  
  setTimeout(() => {
    console.log(8);
    new Promise((resolve) => {
      console.log(9);
      resolve();
    }).then(res => {
      console.log(10);
    }, 0)
  });
  
  console.log(11);

answer:1 -> 6 -> 11 -> 7 -> 2 -> 3 -> 5 -> 4 -> 8 -> 9 -> 10

宏任務(wù)與微任務(wù)

在javascript事件循環(huán)中,==異步**任務(wù)是通過隊列(queue)來存儲的。流程如下:

javascript任務(wù)執(zhí)行順序

而異步任務(wù)一共分為兩種:微任務(wù)與宏任務(wù)。它們的執(zhí)行順序如下:

javascript宏任務(wù)與微任務(wù)執(zhí)行順序

需要注意的是,微任務(wù)與宏任務(wù)是先注冊,再執(zhí)行。而不是讀取到就立即執(zhí)行。

常見的宏任務(wù)(按優(yōu)先級排列):整體代碼script > setImmediate > setTimeout/setInterval。

常見的微任務(wù)(按優(yōu)先級排列):process.nextTickNodejs中的內(nèi)容) > 原生Promise > MutationObserver。

回到剛才的那個題目:

  // 主代碼塊
  console.log(1);
  
  // 注冊了一個宏任務(wù)
  setTimeout(() => {
    // ...
  }, 0);
  
  // {↓標(biāo)記↓}
  new Promise((resolve) => {
    // 立即執(zhí)行部分,其實(shí)是同步任務(wù)
    console.log(6);
    resolve();
  }).then(res => {
    // 這才是微任務(wù)
    console.log(7);
  })
  
  // 注冊了一個宏任務(wù)
  setTimeout(() => {
    // ...
  });
  // 主代碼塊
  console.log(11);

所以一開始先執(zhí)行主代碼塊,輸出1、611,請注意,Promise構(gòu)造函數(shù)的參數(shù)中的代碼是同步任務(wù),與主代碼塊同步進(jìn)行。

執(zhí)行完主代碼塊后,會尋找微任務(wù)隊列中的微任務(wù)并執(zhí)行,此時的微任務(wù)只有標(biāo)記的Promise.then()(其他Promsie所在的代碼塊并未被執(zhí)行,因此尚未被注冊),輸出7。

微任務(wù)執(zhí)行完后,尋找下一個宏任務(wù) — 第一個SetTimeOut。

  setTimeout(() => {
    console.log(2);
    new Promise((resolve) => {
      console.log(3);
      resolve();
    }).then(res => {
      console.log(4);
    })
    console.log(5);
  }, 0);

同理可得:將輸出2、3、5,并注冊一個微任務(wù)(Promise.then())。在執(zhí)行完后執(zhí)行微任務(wù),輸出4。

然后再是最后一個宏任務(wù):

 setTimeout(() => {
    console.log(8);
    new Promise((resolve) => {
      console.log(9);
      resolve();
    }).then(res => {
      console.log(10);
    }, 0)
  });

8、9、10,沒什么問題了吧,別問,問就同理可得。

西江月·證明
即得易見平凡,仿照上例顯然。留作習(xí)題答案略,讀者自證不難。
反之亦然同理,推論自然成立,略去過程Q.E.D ,由上可知證畢。

其實(shí)這個題還差了點(diǎn)意思,換做我就這樣出:

  console.log(1);
  
  setTimeout(() => {
    console.log(2);
    new Promise((resolve) => {
      console.log(3);
      resolve();
    }).then(res => {
      console.log(4);
    })
    console.log(5);
  }, 0);
  
  new Promise((resolve) => {
    console.log(6);
    resolve();
  }).then(res => {
    console.log(7)
    setTimeout(() => {
      console.log(8);
    });
  })
  
  console.log(9);

猜一下答案輸出順序是什么?
...
...
...
...
...
...
...
...
...
答案是 1 -> 6 -> 9 -> 7 -> 2 -> 3 -> 5 -> 4 -> 8。

拓展

常見的宏任務(wù)除了上面提到的那些之外,還有一個不曾提到但非常常見的:IO(輸入輸出流)。你可以簡單理解為事件監(jiān)聽(最常見的表現(xiàn)就是事件監(jiān)聽,不過不僅僅包括事件監(jiān)聽)。

上代碼:

/* css */
div {
  border: 1px solid black;
}

#outer {
  padding: 25px;
  width: 50px;
  background-color: aqua;
}

#inner {
  width: 50px;
  height: 50px;
  background-color: green;
}
<!--html-->
<div id="outer">
  <div id="inner"></div>
</div>
// js
let $outer = document.getElementById('outer');

let $inner = document.getElementById('inner');

function handler() {
  console.log('click') // 直接輸出
  // 注冊微任務(wù)
  Promise.resolve().then(_ => console.log('promise'))
  // 注冊宏任務(wù)
  setTimeout(_ => console.log('timeout'))
  // 注冊宏任務(wù)
  requestAnimationFrame(_ => console.log('animationFrame'));
  // DOM屬性修改,觸發(fā)微任務(wù)
  $outer.setAttribute('data-random', Math.random());
}

// 微任務(wù)
new MutationObserver(_ => {
  console.log('observer')
}).observe($outer, {
  attributes: true
})

$inner.addEventListener('click', handler);
$outer.addEventListener('click', handler);
效果圖

點(diǎn)擊div#inner,輸出結(jié)果: 'click' -> 'promise' -> 'observer' -> 'click' -> 'promise' -> 'observer' -> 'animationFrame' -> 'animationFrame' -> 'timeout' -> 'timeout'。

讓我們來捋一下:

點(diǎn)擊時通過事件冒泡觸發(fā)了宏任務(wù)$inner.click(),輸出'click'。該任務(wù)觸發(fā)了冒泡事件并注冊了宏任務(wù)$outer.click()。并在事件處理函數(shù)handler中注冊了兩個宏任務(wù)(requestAnimationFramesetTimeout)和一個微任務(wù)(Promise),并觸發(fā)了一個微任務(wù)MutationObserver。

該宏任務(wù)執(zhí)行完后,微任務(wù)隊列中有兩個微任務(wù),按優(yōu)先級先執(zhí)行Promise,所以依次輸出'promise','observer'。

下一個宏任務(wù),即$outer.click()開始執(zhí)行,重復(fù)了$inner.click()的事件,區(qū)別是它沒有冒泡并注冊新任務(wù)了。依次輸出'click','promise','observer'。

至此,只剩下幾個宏任務(wù)對線了。

值得注意的是,因為requestAnimationFrame會觸發(fā)頁面重繪(不是優(yōu)先級哦),進(jìn)而會導(dǎo)致setTimeout重置,所以前者會比后者先輸出。

后記

祝你學(xué)習(xí)愉快。

參考文章

  1. 《JS事件循環(huán)機(jī)制(event loop)之宏任務(wù)/微任務(wù)》
  2. 《微任務(wù)、宏任務(wù)與Event-Loop》。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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