Promise為什麼比setTimeout先執行?

doublemeng發表於2019-03-16

基本概念

為便於理解,在進入正題之前,不得不說以下一些基本的概念。

程式與執行緒

程式(process)

  • 是作業系統結構的基礎;

  • 是系統進行資源分配和排程的基本單位;

  • 在linux系統中可以使用ps-ef來查詢程式列表(如下圖)。比程式更小的單位叫做執行緒

    程式列表

執行緒(thread)

  • 是作業系統能夠進行運算排程的最小單位;

  • 它被包含在程式之中,是程式中的實際運作單位;一個程式可以併發多個執行緒,每條執行緒並行執行不同的任務。

併發(concurrency)
  • 一個處理器同時處理多個任務。
並行(parallelism)
  • 多個處理器或多核處理器同時處理多個任務。

單執行緒

  • 單執行緒在程式執行時,所走的程式路徑按照連續順序排下來,前面的必須處理好,後面的才會執行。

同步與非同步

同步(synchronous)

  • 呼叫一旦開始,呼叫者必須等到該呼叫結束才能執行下一步操作。

非同步(asynchronous)

  • 方法一旦呼叫,就會立即返回,呼叫者便可進行下一步操作。

JS執行機制

執行棧

  • 是一個儲存函式呼叫的棧結構,遵循先進後出的原則。

主執行緒

  • 現在正在執行執行棧中的哪個事件。

Event Loop

以上我們講到了程式與執行緒同步與非同步執行棧主執行緒等,那麼JS到底是怎麼執行的呢?

先來供上一個常見面試題,寫出以下程式碼的執行結果

    console.log('start')

    setTimeout(() => {
        console.log('setTimeout')
    }, 0)
    
    new Promise(resolve => {
        console.log('promise')
        resolve()
    }).then(() => {
        console.log('then1')
    }).then(() => {
        console.log('then2');
    })
    
    console.log('end')
複製程式碼

結果如下圖:

執行結果

為什麼setTimeout最後輸出呢???

JS程式碼的執行其實就是往執行棧中放入函式。那麼遇到非同步程式碼的時候該怎麼辦呢?其實當遇到非同步程式碼時,會被掛起並在需要執行的時候加入到任務佇列。一旦執行棧為空,Event Loop 就會從任務佇列中拿出需要執行的程式碼並放入執行棧中執行。

JS引擎常駐於記憶體中,等待宿主將JS程式碼或函式傳遞給它,也就是等待宿主環境分配巨集觀任務,反覆等待 - 執行即為事件迴圈。

Event Loop

Event Loop中,每一次迴圈稱為tick,每一次tick的任務如下:

  • 執行棧選擇最先進入佇列的巨集任務(一般都是script),執行其同步程式碼直至結束;
  • 檢查是否存在微任務,有則會執行至微任務佇列為空;
  • 如有必要會渲染頁面;
  • 開始下一輪tick,執行巨集任務中的非同步程式碼(setTimeout的回撥等)。

來一個超簡單的流程圖

巨集任務與微任務

巨集任務(macrotask)
  • 宿主(Node、瀏覽器)發起的任務;
  • 在ES6規範中,將其稱為task;
  • script、setTimeout、setInterval、I/O、UI rendering、postMessage、MessageChannel、setImmediate
微任務(microtask)
  • JS引擎發起的任務;
  • 在ES6規範中,將其稱為jobs;
  • Promise、MutaionObserver、process.nextTick

參考

掘金技術小冊(前端面試之道)第七小節

Js 的事件迴圈(Event Loop)機制以及例項講解

小結

本文主要介紹了JS執行機制,即Event Loop,及其相關的一系列概念。

如有問題,歡迎指正。

相關文章