Event Loop

本文主要參閱瞭如下兩篇文章,對JS的Event Loop運行機制基礎知識進行了整理。
從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理
JavaScript 運行機制詳解:再談Event Loophtml

背景知識

進程與線程

你們都知道JavaScript是單線程的,這就引伸出一個問題,進程與線程是什麼,他們的區別是什麼?
先給出進程和線程的定義:前端

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

用工廠和工人的例子來形象闡述:ajax

- 進程是一個工廠,工廠有它的獨立資源 -> 系統分配的內存(獨立的一塊內存)

- 工廠之間相互獨立 -> 進程之間相互獨立

- 線程是工廠中的工人,多個工人協做完成任務 -> 多個線程在進程中協做完成任務

- 工廠內有一個或多個工人 -> 一個進程由一個或多個線程組成

- 工人之間共享工廠的資源 -> 同一進程下的各個線程之間共享進程的內存空間(包括代碼段、數據集、堆等)

補充:segmentfault

  • 咱們所說的單線程和多線程,是指一個進程內是單一線程仍是多線程。
  • 進程間的通訊方式包括: 管道pipe、 命名管道FIFO、消息隊列MessageQueue、共享存儲SharedMemory、信號量Semaphore、套接字Socket、信號。

瀏覽器是多進程的

關於瀏覽器進程問題能夠簡單基礎三點:瀏覽器

  • 瀏覽器是多進程的。
  • 瀏覽器之因此可以運行,是由於系統給它的進程分配了資源(cpu、內存)。
  • 簡單點理解,每打開一個Tab頁,就至關於建立了一個獨立的瀏覽器進程。

平時 coding 接觸到最多的一個瀏覽器進程是瀏覽器渲染進程(瀏覽器內核),它管理着頁面渲染。腳本執行,事件處理等。要同時處理這麼多事情,渲染進程顯然是多線程的,它主要包括如下5個常駐線程:多線程

  1. GUI渲染線程,負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等。
  2. JS引擎線程,也稱爲JS內核,負責處理Javascript腳本程序,(例如V8引擎)。
  3. 事件觸發線程,用來控制事件循環(能夠理解爲,JS引擎線程本身都忙不過來,須要瀏覽器另開線程協助)。
  4. 定時觸發器線程,瀏覽器定時計數器並非由JavaScript引擎計數的,(由於JavaScript引擎是單線程的, 若是處於阻塞線程狀態就會影響記計時的準確),JS中經常使用的setIntervalsetTimeout就歸這個線程管理。
  5. 異步http請求線程,也就是ajax發出http請求後,接收響應、檢測狀態變動等都是這個線程管理的。

咱們常說的JavaScript是單線程的,其實就是說的JS引擎是單線程的,它僅僅是瀏覽器渲染進程種的一個線程。爲何呢?由於JavaScript的主要做用是與用戶互動,以及操做DOM,若是JavaScript有兩個線程,一個線程對一個DOM節點執行 A 操做,另外一個線程這個DOM節點執行 B 操做,那麼就會起衝突,因此JavaScript在前端的應用就註定了它是單線程的。異步

然而JavaScript的單線程特性就註定咱們不用它去完成密集的 cpu 運算,由於密集 cpu 運算耗時過長,阻塞頁面渲染。爲了解決這個問題,HTML5提出 Web Worker 標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM函數

Event Loop

JavaScript 是單線程的帶來的問題是:全部任務都必須同步執行,問題就出現了,不少 I/O 過程是很是耗時的(如http 請求數據),若是要等到 I/O 過程結束再執行後續任務,就會出現頁面的卡頓、cpu 的閒置。因而異步的任務就出現了,異步任務是指掛起處於等待中的任務,繼續執行同步任務,等到結果返回再去繼續執行被掛起的任務。因而,JavaScript 的任務能夠分爲同步任務和異步任務。下面就引出 Event Loop 機制:oop

  • 全部同步任務都在主線程上執行,造成一個執行棧
  • 主線程以外,事件觸發線程管理着一個任務隊列,只要異步任務有了運行結果,就在任務隊列之中放置一個事件。
  • 一旦執行棧中的全部同步任務執行完畢(此時JS引擎空閒),系統就會讀取任務隊列,將可運行的異步任務添加到執行棧中,開始執行。

clipboard.png

如上圖所示,執行棧中的代碼會調用一個異步的API,它們會在任務隊列中添加各類事件(或者說回調函數),另外用戶的操做如clickmousedown等都會在任務隊列中添加事件。只要執行棧中的代碼執行完畢,主線程就會去讀取任務隊列,將可執行的回調函數放到執行棧中執行。佈局

總結一下:

執行棧執行完畢 -> 主線程讀取任務隊列,並執行回調函數 -> 執行棧執行完畢 -> 主線程讀取任務隊列,並執行回調函數 ...

這個過程一直循環下去,因此就叫事件循環(Event Loop)。

setTimeout 和 setInterval

前面提到了瀏覽器的定時觸發器線程,它的主要做用就是計時,setTimeoutsetInterval 就由它來控制,原理就是到達設置時間後,往任務隊列中添加這兩個函數中指定的回調函數。

setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式。可是須要注意的是,實際是計時結束後定時觸發器線程纔會將回調函數放到任務隊列中去,此時任務隊列中這個回調以前可能已經有一些事件待處理,而且必定要執行棧的任務執行完後纔會開始執行任務隊列中的任務,因此 setTimeout() 中回調開始執行的時間是:執行棧執行時間 + 任務隊列前方回調執行時間 + 延遲時間

setInterval() 方法可按照指定的時間間隔來週期性調用函數或計算表達式。它的問題在於:每次都精確的隔一段時間將一個回調放到任務隊列中,並無考慮到內部回調函數執行所需時間,這就會致使兩種問題:

  • 回調函數執行須要時間,兩個函數執行的時間間隔會小於設定值;
  • 若是回調函數執行時間大於設定間隔,就會出現上一個加入任務隊列中的回調還沒執行完,下一個回調就被加入任務隊列了,就會出現累計效應,即後面的回調會連續執行。
相關文章
相關標籤/搜索