瀏覽器中的Event Loop

JavaScript 語言的一大特色是單線程。前端

線程和進程

  • 線程是 cpu 調度的最小單位(線程是進程上的一次程序運行單位,一個進程能夠有多個線程)。
  • 進程是 cpu 資源分配的最小單位(是能擁有資源和獨立運行的最小單位)。

瀏覽器是多進程的

每開一個標籤頁系統就建立一個獨立的進程,一個進程中可能包含如渲染線程、JS 引擎線程、HTTP 請求線程等。
注意:因爲瀏覽器的優化機制某些進程可能會被合併。面試

爲何JavaScript是單線程的

若是 JavaScript 是多線程的,假定 procces1 和 procces2 同時對同一個 DOM 做出操做,瀏覽器應該以哪個爲準,因此 JavaScript 只能爲單線程。瀏覽器

事件循環

任務分爲同步任務和異步任務,同步任務會進入主線程,異步任務則會進入事件隊列 ( Event Queue )。主線程中的任務執行完畢後會從事件隊列中取出放入執行棧。bash

任務除了分爲同步任務和異步任務,還分爲微任務和宏任務。多線程

  • 微任務(microtask)包括:process.nextTick,Promise,MutationObserver
  • 宏任務(macrotask)包括:主代碼script,setTimeout,setInterval

事件循環的執行順序爲異步

  • 事件循環開始,script 代碼塊作爲宏任務進入主線程執行
  • 同步任務移入主線程執行,異步任務進入事件隊列
  • 主線程執行完成後,取出事件隊列中的微任務執行
  • 微任務執行完畢後,事件循環結束
  • 取出事件隊列中的宏任務,開始新一輪事件循環
  • ......

示例:函數

setTimeout(() => {
  new Promise((resolve) => {
    console.log('1')
    resolve()
  }).then(() => {
    console.log('2')
  })
  console.log('3')
}, 0)

console.log('4')

new Promise(resolve => {
  console.log('5')
  resolve();
}).then(() => {
  console.log('6')
})

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

複製代碼

分析:優化

  • 第一輪事件循環:ui

    • script 做爲宏任務進入主線程
    • 遇到 setTimeout,註冊其回調函數做爲宏任務移入事件隊列,記爲 setTimeout1
    • 遇到 console.log,輸出'4'
    • 遇到 Promise,輸出'5',註冊其回調函數做爲微任務進入事件隊列,記爲 then
    • 遇到 setTimeout,註冊其回調函數做爲宏任務移入事件隊列,記爲 setTimeout2
    • 宏任務執行完畢,執行事件隊列中的微任務 then,輸出'6'

    第一輪事件循環結束輸出'4'、5'、'6'spa

  • 第二輪事件循環:

    • 執行宏任 setTimeout1
    • 遇到 Promise,輸出'1',註冊其回調函數做爲微任務進入事件隊列,記爲 then
    • 遇到 console.log,輸出'3'
    • 宏任務執行完畢,執行事件隊列中的微任務 then,輸出'2'

    第二輪事件循環結束輸出'1'、'3'、'2'

  • 第三輪事件循環:

    • 遇到 Promise,輸出'7',註冊其回調函數做爲微任務進入事件隊列,記爲 then
    • 遇到 console.log,輸出'9'
    • 宏任務執行完畢,執行事件隊列中的微任務 then,輸出'8'

    第三輪事件循環結束輸出'7'、'9'、'8'

    代碼執行完畢輸出結果爲'4'、5'、'6'、'1'、'3'、'2'、'7'、'9'、'8'

參考:前端面試之道

相關文章
相關標籤/搜索