1. 程式人生 > 其它 >js的事件環(事件迴圈)機制

js的事件環(事件迴圈)機制

目錄

前言

js是一門單執行緒的語言,就是說,在js中,永遠都只有一個主執行緒在處理任務。因為js的執行環境在瀏覽器中,瀏覽器需要隨時與使用者進行互動,需要進行各種各樣的dom操作,如果js是多執行緒的,那麼就有可能出現一個執行緒在刪除dom,一個執行緒在操作這個dom,程式就會出現不可預料的bug。所以js選擇只有一個主執行緒執行程式碼,保證程式執行的一致性。

web worker 技術在一定程度上解決了js單執行緒的問題,它新開了多個子執行緒幫助主執行緒處理任務,一定程度上加快了js處理任務的效率。但是這些子執行緒也有一些侷限性,他們不能獨立執行,並且也不能執行I/O操作。

雖然js是單執行緒的,但它有一個很重要的特點——非同步非阻塞。那麼,它作為單執行緒的語言,是如何實現非同步非阻塞的呢?這就是這篇文章的主要討論內容。

事件迴圈(事件環)

js 中的任務在大方向上分為兩類:同步和非同步。

其中,同步任務按順序依次執行,而非同步任務(如ajax請求資料)則在結果返回之前先處於掛起狀態,等結果返回之後再執行回撥函式中的邏輯程式碼。

那麼,JavaScript引擎怎麼對其進行控制呢?就是依賴於事件環機制,瞭解事件環機制之前首先需要引入兩個概念——執行棧和事件佇列。

執行棧

剛剛提到,js在執行同步程式碼時,會按照程式碼順序依次執行,其實js在執行一個檔案時,首先會解析其中的程式碼,將其中的同步程式碼依次放入執行棧中,然後從頭開始順序執行。

比如,js遇到一個方法需要執行,那麼它會將這個方法的執行環境(方法的作用域、上層作用域的指向、方法的引數、方法中的區域性變數、這個作用域的this物件)新增到執行棧中,然後進入這個執行環境執行其中的程式碼,執行完後,js就會退出這個執行環境並將其銷燬,返回上一層的執行環境。這個過程會一直迴圈,直到執行棧中的程式碼全部執行完畢。

事件佇列

而非同步任務,js 會在其返回結果之前將其掛起,繼續執行執行棧中的後續程式碼,當這個非同步任務返回結果後,js 會將這個非同步任務加入到 事件佇列 中,等待當前執行棧中的所有任務都執行完畢後,主執行緒處於閒置狀態,它就會去查詢事件佇列中的任務,然後按照先進先出的順序,取出之前放入的非同步事件,並把對應事件的回撥函式放入執行棧中,開始執行其中的同步程式碼。這個過程不斷重複,就形成了一個無限迴圈,這個過程也稱之為“事件迴圈”。

非同步任務分類:微任務和巨集任務

非同步任務還可細分為 微任務( micro task ) 和 巨集任務( macro task ),setInterval() 和 setTimeout() 方法屬於巨集任務,Promise 方法屬於微任務。

相同的,事件佇列也分為微任務佇列和巨集任務佇列。微任務的執行優先順序高於巨集任務。即在當前執行棧為空時,主執行緒會優先檢視微任務佇列中的事件,依次執行對應事件的回撥函式,直到微任務佇列為空,再去巨集任務佇列查詢、處理事件。

擴充套件:node 環境下的事件迴圈機制

node 環境中的事件迴圈機制其實與瀏覽器中的大致一致,只是依靠的引擎不同,node 中的事件迴圈依靠 libuv 引擎。chrome v8 引擎作為node的直譯器,將node中的js程式碼分析後去呼叫對應的node api,這些api最終都由libuv 引擎驅動,執行對應任務,對其進行區分,放在不同的佇列中等待主執行緒執行。

從上圖可看出 node 中事件迴圈的順序,從incoming->poll->check->close callbacks->timers->I/O callbacks->idle,prepare

各個階段的大致功能:

  • poll:處理I/O事件;檢視 poll queue 中是否有待處理的任務,如果有則按先進先出的順序執行

  • check:執行 setImmediate() 的回撥函式;poll queue中的任務處理完畢後,進入該階段

  • close callbacks:執行 close 事件的回撥函式;當一個socket連線或者一個handle被突然關閉時(例如呼叫了socket.destroy()方法),close事件會被髮送到這個階段執行回撥。否則事件會用process.nextTick()方法傳送出去。

    process.nextTick():用來推遲任務與執行,效果與定時器和setImmediate()類似
    
    使用process.nextTick()傳送的事件會存入 nextTick queue 中,這個佇列中的事件會在每一個階段執行完畢,準備進入下一個階段之前優先執行,即當事件迴圈準備進入下一個階段之前,會先檢查nextTick queue中是否有任務,如果有,那麼會先將其中的任務執行完畢
    
    setTimeout中的回撥會在我們所指定的時間間隔後第一時間去執行(timers 階段)
    
    setImmediate是在固定的階段執行(check 階段)
    
    所以 setTimeout 和 setImmediate 的執行順序並不固定,但可以確定的是,在一個I/O事件的回撥函式中,它們的順序是固定的,即 setImmediate 在 setTimeout 之前,因為 check 階段在 timers 階段之前執行
    
  • timers:執行 setInterval() 、setTimeout() 定時器中的回撥函式

  • I/O callbacks:執行大部分事件的回撥函式,除了 close 事件、定時器 和 setImmediate()

  • idle,prepare:在node內部使用