setTimeout 是到了xx ms 就執行嗎,瞭解瀏覽器的 Event-Loop 機制

yeshan333發表於2020-12-26

要想 JavaScript 玩得溜,還得了解波 JavaScript 執行機制/(ㄒoㄒ)/~~。

個人部落格:https://shansan.top

前言

最近看了波 JavaScript 相關的文章,不得不說,JavaScript 我還真沒玩明白(給我哭~。。。?)。也挺久沒寫文了,實習(“摸?”)之餘小記一波。

回顧一句話:JavaScript 是一門單執行緒、非阻塞、非同步、解釋性指令碼語言。

本文的標題是:setTimeout 是到了xx ms 就執行嗎,瞭解 Event-Loop 機制。先回答波:不是。

來看下網上的一段經典 js 程式碼在瀏覽器中「Microsoft Edge 84.0.522.63(64位)」的執行結果。

console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
},0);
Promise.resolve().then(() => {
  console.log('promise1');
}).then(() =>{
  console.log("promise2");
});
console.log('script end');

執行結果

可以明顯看到 setTimeout 的 callback 並非在 0 ms 後立即執行。那麼,這是問什麼?要了解原因,需要了解後續介紹的 Event Loop 機制。

概念一覽

  • 瀏覽器的核心-多執行緒的渲染程式:頁面的渲染、js 的執行、事件的迴圈都在渲染程式中進行。渲染程式主要包含以下幾個執行緒:

{% fancybox %}
JS核心中的執行緒
{% endfancybox %}

  • Task:Task 有 MicroTask 和 MacroTask 之分,MicroTask 在 Promise 出現之後引入。MacroTask 和 MicroTask 分別在以下幾種場景形成:
    • MacroTask:主程式碼塊、setTimeout、setInterval、IO 事件等。
    • MicroTask:Promise、process.nextTick 等。

瀏覽器中的Event Loop

有了基礎概念,讓我們來了解一下文章開頭給出的程式碼是怎麼執行的,程式碼如下:

console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
},0);
Promise.resolve().then(() => {
  console.log('promise1');
}).then(() =>{
  console.log("promise2");
});
console.log('script end');
  • 1、首先,整個程式碼塊作為第一個 MacroTask 被執行,同步的程式碼直接被壓入執行棧被執行「同步任務在JS引擎執行緒上執行」,script start 和 script end 被列印;
  • 2、setTimeout 被作為 MacroTask 處理,加入巨集任務佇列中;
  • 3、Promise 被作為 MicroTask 處理,加入微任務佇列中;
  • 4、本次 MacroTask 處理完畢,檢查微任務佇列,發現 promise then 的 callback,promise1,promise2 先後列印;
  • 5、接下來執行下一個 MacroTask,即 setTimeout 推送給任務佇列的 callback,列印 setTimeout。

so,程式碼執行結果如下:

script start
script end
promise1
promise2
setTimeout

由此,可大致瞭解到瀏覽器下 Event-Loop 執行機制大致如下:

{% folding open red, Event-Loop 執行機制 %}

  • 1、一開始,整段指令碼被當作 MacroTask 執行
  • 2、執行過程中,同步程式碼進入可執行棧中直接執行,MacroTask 進入巨集任務佇列,MicroTask 進入微任務佇列
  • 3、當前 MacroTask 執行完就出隊,檢查微任務佇列,如果不為空,則依次執行微任務佇列中的 MicroTask,直到微任務佇列為空
  • 4、執行瀏覽器的 UI 執行緒的渲染工作「兩個 MicroTask 執行空隙,有次 render 工作」
  • 6、執行隊首的 MacroTask,回到 2,依此迴圈,直至巨集任務佇列和微任務佇列都為空

可通過下圖簡單理解一波:

瀏覽器 Event-Loop 簡覽

{% endfolding %}

由此可知道,setTimeout 中的 callback 不能按時執行是因為 Event-Loop,導致 JS 引擎執行緒還有其它的 task (promise MicroTask)要處理,主執行緒還未空閒下來。

參考

相關文章