瀏覽器事件循環機制

<a href="https://blog.csdn.net/qq_37205708/article/details/98188222">原文地址</a>前端

github 獲取更多前端資源

https://github.com/ChenMingK/WebKnowledges-Notesnode

靈魂三問

JavaScript 爲何是單線程的?JavaScript 爲何須要異步?JavaScript 單線程又是如何實現異步的?git

<span style="font-size: 20px;">1.JavaScript 爲何是單線程的?</span>github

如今有 2 個線程 process1 process2,假設 JavaScript 是多線程的,因此他們能夠對同一個 dom 同時進行操做。process1 刪除了該 dom,而 process2 編輯了該 dom,同時下達 2 個矛盾的命令,瀏覽器究竟該如何執行呢?這樣想,JavaScript 爲何被設計成單線程應該就容易理解了吧。promise

<span style="font-size: 20px;">2.JavaScript 爲何須要異步?</span>瀏覽器

若是 JavaScript 中不存在異步,只能自上而下執行,若是上一行解析時間很長,那麼下面的代碼就會被阻塞。對於用戶而言,阻塞就意味着"卡死",這樣就致使了不好的用戶體驗,因此 JavaScript 中存在異步執行。網絡

<span style="font-size: 20px;">3.JavaScript 單線程又是如何實現異步的呢?</span>多線程

是經過事件循環來實現的dom

爲了利用多核 CPU 的計算能力,HTML5 提出 Web Worker 標準,容許 JavaScript 腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做 DOM。因此,這個新標準並無改變 JavaScript 單線程的本質。異步

爲何說 JavaScript 是「非阻塞」的語言?非阻塞即:「程序不會由於某個任務而停下來」

console.log("程序時間:" + new Date().getTime())

setTimeout(function () {
     console.log("暫停一秒:" + new Date().getTime())
}, 1000)

console.log('這是暫停一秒以後的時間:' + new Date().getTime())

簡單來,若是上圖的 setTimeout 換成 C++ 的 sleep(1000) 之類的,那麼 C++ 是會確實地「睡眠」那麼段時間的,而 JS 不會。若是我就是想實現這個功能呢?能夠利用 Promise 實現:

async function test () {
  console.log('start')
  await sleep(3000)
  console.log('3s has passed')
}

function sleep (ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, ms)
  })
}

任務隊列

當遇到一個異步事件後,JavaScript 引擎並不會一直等待異步事件返回結果,而是會將這個事件掛在與執行棧不一樣的隊列中,咱們稱之爲任務隊列。 這些任務又被細分爲宏任務和微任務

  • 宏任務(macrotask):script(全局任務),setTimeout ,setInterval ,setImmediate (node.js 獨有),I/O(磁盤讀寫或網絡通訊) ,UI rendering(UI交互事件)
  • 微任務(microtask):process.nextTick (node.js 獨有), Promise.then, Object.observer(已廢棄), MutationObserver

事件循環(event loop)

這裏首先要明確一點:瀏覽器是一個進程,其有多個線程(具體見 Broswer 章節) 通常狀況下, 瀏覽器有以下五個線程:

  • GUI 渲染線程
  • JavaScript 引擎線程
  • 瀏覽器事件觸發線程
  • 定時器觸發線程
  • 異步 HTTP 請求線程

GUI 渲染線程和 JavaScript 引擎線程是互斥的,其餘線程相互之間都是能夠並行執行的。 瀏覽器中,JavaScript 引擎循環地從任務隊列中讀取任務並執行,這種運行機制就叫作事件循環。

在這裏插入圖片描述

更準確地說,事件循環是按下面幾個步驟執行的:

  1. 執行同步代碼,這屬於宏任務(初始時的同步代碼就是 script 總體代碼)
  2. 執行棧爲空,查詢是否有微任務 (microtask) 須要執行
  3. 執行全部微任務
  4. 必要的話渲染 UI
  5. 而後開始下一輪 Event loop,執行宏任務中的異步代碼

例題

setTimeout(function () {
  console.log(1)
}, 0)

Promise.resolve(function () {
  console.log(2)
})

new Promise(function (resolve) {
  console.log(3)
})

console.log(4)

// 上述代碼的輸出結果是什麼???

正確答案是-------------------> 3 4 1 解釋以下:

// 遇到 setTimeout 將 setTimeout 回調放入宏任務隊列中
setTimeout(function () {
  console.log(1)
}, 0)

// 遇到了 promise,可是並無 then 方法回調 
// 因此這句代碼會在執行過程當中進入咱們當前的執行上下文 緊接着就出棧了
Promise.resolve(function () {
  console.log(2)
})

// 遇到了一個 new Promise,Promise 有一個原則就是在初始化 Promise 的時候Promise 內部的構造器函數會當即執行,
// 所以在這裏會當即輸出一個 3,因此這個 3 是第一個輸出的
new Promise(function (resolve) {
  console.log(3)
})
// 而後第二個輸出 4  當代碼執行完畢後回去微任務隊列查找有沒有任務,
// 發現微任務隊列是空的,那麼就去宏仁務隊列中查找,發現有一個咱們剛剛放進去的setTimeout 回調函數,
// 那麼就取出這個任務進行執行,因此緊接着輸出1
console.log(4)

太簡單了?來看看這題:

console.log('begin'); // 1.begin
setTimeout(() => {
    console.log('setTimeout 1'); // 3.setTimeout 1
    Promise.resolve() // Promise.resolve().then :直接把 then 回調放入微任務隊列
        .then(() => {
            console.log('promise 1'); // 5.promise 1
            setTimeout(() => {
                console.log('setTimeout2'); // 8. setTimeout2
            });
        })
        .then(() => {
            console.log('promise 2'); // 7. promise 2  注意7比8要快,then 方法返回一個新的 Promise 對象
        });
    new Promise(resolve => {
        console.log('a'); // 4. a
        resolve();
    }).then(() => {
        console.log('b'); // 6. b
    });
}, 0);
console.log('end'); // 2.end
相關文章
相關標籤/搜索