JavaScript的事件迴圈(Event loop)(附圖)

caihaihong發表於2019-03-02
這是本人的理解,並且Google browser斷點實踐的,不同瀏覽器可能存在差異。章末附帶外文視訊地址。

JS特點

javascript一門單執行緒的非阻塞的指令碼語言。當使用者進行點選元素,又進行元素移除,這樣就會導致哪個事件的優先順序不知道,所以JS必須是單執行緒的。為了協調事件,使用者互動,指令碼,UI渲染,和網路處理,Event Loop可以防止主執行緒阻塞。在某些花費時間較長的事件,瀏覽器會將它們掛起(pending),事件等待被執行,事件迴圈是通過任務佇列實現的。

one thread == one callback == one thing at a time.

Event Loop
  • Browing Context
  • Worker 兩者是獨立的,有各自的執行緒環境。
棧(Stack)

棧是一種遵循後進先出(LIFO)的資料集合,新新增或待刪除的元素都儲存在棧的末尾,稱作棧頂,另一端稱作棧底。在棧裡,新元素都靠近棧頂,舊元素都靠近棧底

佇列(Queue)

佇列是一種遵循先進先出(FIFO)的資料集合,新的條目會被加到佇列的末尾,舊的條目會從佇列的頭部被移出。

微任務與任務
  • 任務task:script(整體程式碼)、setTimeout、setInterval、I/O(輸入輸出裝置)、UI互動事件,setImmediate(Node.js環境),webAPI callback()等
  • 微任務:Promise、MutationObserver、process.nextTick(Node.js環境)

JS執行規則

可以這樣理解,有兩個佇列—— 執行佇列(同步任務synchronous)、事件佇列(非同步任務asynchronous),存放所有著JS執行的任務(ajax、點選事件、UI渲染等、promise.then)。

  • 執行佇列存放所有同步程式碼的任務
  • 事件佇列存放所有非同步程式碼的巨集任務
  • 微任務處於兩個佇列之間

執行Js程式碼,從上往下執行程式碼,同步程式碼,依次執行,遇到非同步程式碼,則根據其任務型別,新增到相應的對應佇列,巨集任務放入事件佇列,微任務放於執行佇列之後(但是會是某個事件迴圈的最後),事件佇列(任務)之前。這個處理事件過程是不斷迴圈的,只要主執行緒空了,就會去讀取"任務佇列"並且執行。

demo
<script>
    console.log('script start');

    setTimeout(function() {
        console.log('timeout1');
    }, 0);

    new Promise(resolve => {
        console.log('promise1');
        resolve();
        setTimeout(() => {
            console.log('timeout2');
            new Promise(resolve => {
                resolve();
            }).then(function() {
                console.log('then3')
            })
        }, 0);
    }).then(function() {
        console.log('then1')
    })
    new Promise(resolve => {
        resolve();
    }).then(function() {
        console.log('then2')
    })

    console.log('script end');
</script>

複製程式碼

image

總結任務

  • 任務(tasks)按循序執行,瀏覽器或許在一邊執行任務(tasks)一邊更新渲染
  • 微任務(microtasks)也是按順序執行
    • 在每一次回撥之後執行,只要沒有其他js正在執行
    • 在每一次任務之後
  • 當前執行棧執行完畢時會立刻先處理所有微任務佇列中的事件,然後再去巨集任務佇列中取出一個事件。
參考

相關文章