JavaScript事件循環(Event Loop)機制

JavaScript 是單線程單併發語言

  1. 什麼是單線程javascript

    主程序只有一個線程,即同一時間片段內其只能執行單個任務。html

  2. 爲何選擇單線程?java

    JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。git

  3. 單線程意味着什麼?github

    單線程就意味着,全部任務都須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就須要一直等着。這就會致使IO操做(耗時但cpu閒置)時形成性能浪費的問題。promise

  4. 如何解決單線程帶來的性能問題?瀏覽器

    答案是異步!主線程徹底能夠無論IO操做,暫時掛起處於等待中的任務,先運行排在後面的任務。等到IO操做返回告終果,再回過頭,把掛起的任務繼續執行下去。因而,全部任務能夠分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)網絡

    注: 當主線程阻塞時,任務隊列仍然是可以被推入任務的併發

事件循環(Event Loop)

  1. JavaScript 內存模型異步

    講事件循環以前,先看一張下網上看到的 JavaScript 內存模型,相信看完這個會對事件循環機制有一種豁然開朗的感受。

    • 調用棧(Call Stack):用於主線程任務的執行
    • 堆(Heap): 用於存放非結構化數據,譬如程序分配的變量與對象
    • 任務隊列(Queue): 用於存放異步任務與定時任務。
  2. JavaScript 代碼執行機制:

    • 全部同步任務都在主線程上的棧中執行。
    • 主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
    • 一旦"棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",選擇出須要首先執行的任務(由瀏覽器決定,並不按序)。
  3. Event Loop

    如今咱們來聊事件循環。事件循環顧名思義它就是一個循環,主線程會不斷循環執行上面的第三步,其基本的代碼邏輯以下所示:

    while (queue.waitForMessage()) {
    queue.processNextMessage();
    }
  4. 常見異步任務進入任務隊列時機

    行爲 時機
    DOM操做 在用戶點擊等操做事件完成後
    網絡操做(Ajax等) 在網絡操做響應後
    定時器 在規定時間到達後

    事件循環機制圖解:

任務

  1. MacroTask(Task)
    setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering

  2. MicroTask(在ES2015規範中稱爲Job)
    process.nextTick, Promise, Object.observe, MutationObserver

規範:

  • 每一個瀏覽器環境,至多有一個event loop。
  • 一個event loop能夠有1個或多個task queue,而僅有一個 MicroTask Queue。
  • 一個task queue是一列有序的task, 每一個task定義時都有一個task source,從同一個task source來的task必須放到同一個task queue,從不一樣源來的則被添加到不一樣隊列。
  • tasks are scheduled,因此瀏覽器能夠從內部到JS/DOM,保證動做按序發生。
  • Microtasks are scheduled,Microtask queue 在當前 task queue 的結尾執行。microtask中添加的microtask也被添加到Microtask queue的末尾並處理。

注: event loop的每一個turn,是由瀏覽器決定先執行哪一個task queue。這容許瀏覽器爲不一樣的task source設置不一樣的優先級,好比爲用戶交互設置更高優先級來使用戶感受流暢。

示例

function ELoop() {
    // 當前任務
    let p = new Promise((resolve, reject)=>{
        console.log("current Task")
        resolve();
    });
    let nextP; 

    setTimeout(()=>{
        console.log("MacroTask_1");
        nextP.then(()=>{
            // 第一次執行時,這段代碼並無執行到。
            console.log("MicroTask_promise_1"); //第一個MicroTask
        })
        console.log("MacroTask_1 end")
    }, 0) // 第一個 MacroTask

    setTimeout(()=>{
        console.log("MacroTask_2");
        console.log("MacroTask_2 end")
    }, 0)// 第二個MacroTask

    nextP = p.then(()=>{
        console.log("MicroTask_promise_2"); //第一個MicroTask
        console.log(1)
    }).then(()=>{
        console.log("MicroTask_promise_3"); // 第二個MicroTask
        console.log(1)
    })

    console.log("current Task end")
}

ELoop();

/**輸出結果:
current Task
MicroTask_promise_2
MicroTask_promise_3
MacroTask_1
MicroTask_promise_1
MacroTask_2
**/

參考文獻:

從Promise來看JavaScript中的Event Loop、Tasks和Microtasks

JavaScript Event Loop 機制詳解與 Vue.js 中實踐應用

JavaScript 運行機制詳解:再談Event Loop

相關文章
相關標籤/搜索