Js 的事件循環(Event Loop)機制以及實例講解

前言

你們都知道js是單線程的腳本語言,在同一時間,只能作同一件事,爲了協調事件、用戶交互、腳本、UI渲染和網絡處理等行爲,防止主線程阻塞,Event Loop方案應運而生...html

我的博客瞭解一下: obkoro1.com

爲何js是單線程?

js做爲主要運行在瀏覽器的腳本語言,js主要用途之一是操做DOM。面試

在js高程中舉過一個栗子,若是js同時有兩個線程,同時對同一個dom進行操做,這時瀏覽器應該聽哪一個線程的,如何判斷優先級?ajax

爲了不這種問題,js必須是一門單線程語言,而且在將來這個特色也不會改變。瀏覽器


執行棧與任務隊列

由於js是單線程語言,當遇到異步任務(如ajax操做等)時,不可能一直等待異步完成,再繼續往下執行,在這期間瀏覽器是空閒狀態,顯而易見這會致使巨大的資源浪費。網絡

執行棧

當執行某個函數、用戶點擊一次鼠標,Ajax完成,一個圖片加載完成等事件發生時,只要指定過回調函數,這些事件發生時就會進入執行棧隊列中,等待主線程讀取,遵循先進先出原則。dom

主線程

要明確的一點是,主線程跟執行棧是不一樣概念,主線程規定如今執行執行棧中的哪一個事件。異步

主線程循環:即主線程會不停的從執行棧中讀取事件,會執行完全部棧中的同步代碼。函數

當遇到一個異步事件後,並不會一直等待異步事件返回結果,而是會將這個事件掛在與執行棧不一樣的隊列中,咱們稱之爲任務隊列(Task Queue)。oop

當主線程將執行棧中全部的代碼執行完以後,主線程將會去查看任務隊列是否有任務。若是有,那麼主線程會依次執行那些任務隊列中的回調函數。post

不太理解的話,能夠運行一下下面的代碼,或者點擊一下這個demo

結果是當a、b、c函數都執行完成以後,三個setTimeout纔會依次執行。

let a = () => {
  setTimeout(() => {
    console.log('任務隊列函數1')
  }, 0)
  for (let i = 0; i < 5000; i++) {
    console.log('a的for循環')
  }
  console.log('a事件執行完')
}
let b = () => {
  setTimeout(() => {
    console.log('任務隊列函數2')
  }, 0)
  for (let i = 0; i < 5000; i++) {
    console.log('b的for循環')
  }
  console.log('b事件執行完')
}
let c = () => {
  setTimeout(() => {
    console.log('任務隊列函數3')
  }, 0)
  for (let i = 0; i < 5000; i++) {
    console.log('c的for循環')
  }
  console.log('c事件執行完')
}
a();
b();
c();
// 當a、b、c函數都執行完成以後,三個setTimeout纔會依次執行

js 異步執行的運行機制。

  1. 全部任務都在主線程上執行,造成一個執行棧。
  2. 主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
  3. 一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列"。那些對應的異步任務,結束等待狀態,進入執行棧並開始執行。
  4. 主線程不斷重複上面的第三步

宏任務與微任務:

異步任務分爲 宏任務(macrotask) 與 微任務 (microtask),不一樣的API註冊的任務會依次進入自身對應的隊列中,而後等待 Event Loop 將它們依次壓入執行棧中執行。

宏任務(macrotask):

script(總體代碼)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 環境)

微任務(microtask):

Promise、 MutaionObserver、process.nextTick(Node.js環境)

Event Loop(事件循環):

Event Loop(事件循環)中,每一次循環稱爲 tick, 每一次tick的任務以下:

  • 執行棧選擇最早進入隊列的宏任務(一般是script總體代碼),若是有則執行
  • 檢查是否存在 Microtask,若是存在則不停的執行,直至清空 microtask 隊列
  • 更新render(每一次事件循環,瀏覽器均可能會去更新渲染)
  • 重複以上步驟

宏任務 > 全部微任務 > 宏任務,以下圖所示:

從上圖咱們能夠看出:

  1. 將全部任務當作兩個隊列:執行隊列與事件隊列。
  2. 執行隊列是同步的,事件隊列是異步的,宏任務放入事件列表,微任務放入執行隊列以後,事件隊列以前。
  3. 當執行完同步代碼以後,就會執行位於執行列表以後的微任務,而後再執行事件列表中的宏任務

上面提到的demo結果能夠這麼理解:先執行script宏任務,執行完了以後,再執行其餘兩個定時器宏任務。


面試題實踐

下面這個題,不少人都應該看過/遇到過,從新來看會不會以爲清晰不少:

// 執行順序問題,考察頻率挺高的,先本身想答案**
    setTimeout(function () {
        console.log(1);
    });
    new Promise(function(resolve,reject){
        console.log(2)
        resolve(3)
    }).then(function(val){
        console.log(val);
    })
    console.log(4);

根據本文的解析,咱們能夠獲得:

  1. 先執行script同步代碼

    先執行new Promise中的console.log(2),then後面的不執行屬於微任務
    而後執行console.log(4)

  2. 執行完script宏任務後,執行微任務,console.log(3),沒有其餘微任務了。
  3. 執行另外一個宏任務,定時器,console.log(1)。

根據本文的內容,能夠很輕鬆,且有理有據的猜出寫出正確答案:2,4,3,1.


結語

相似上文的面試題還有不少,實則都大同小異,只要掌握了事件循環的機制,這些問題都會變得很簡單。

文章若有不正確的地方歡迎各位路過的大佬鞭策!但願你們看完能夠有所收穫,喜歡的話,趕忙點波訂閱關注/喜歡。

看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。

我的blog and 掘金我的主頁,如需轉載,請放上原文連接並署名。碼字不易,感謝支持!

若是喜歡本文的話,歡迎關注個人訂閱號,漫漫技術路,期待將來共同窗習成長。

以上2018.6.16

參考資料:

詳解JavaScript中的Event Loop(事件循環)機制

JavaScript中的事件循環 Event Loop

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

相關文章
相關標籤/搜索