深刻理解JavaScript之Event Loop

前言

最近閱讀《高性能JavaScript》時,第六章談到「經過定時器將JavaScript執行代碼的控制權先讓給瀏覽器用於更新UI狀態,而後再將控制權交回給JavaScript代碼,這樣就可使得頁面更爲流暢」,就聯想到了以前理解的事件循環。html

這篇文章就是爲了解釋爲何這麼作能夠提高頁面的流暢度。html5

事件循環(Event Loop)

Internal.gif

單線程的JavaScript

總所周知,JavaScript語言的一大特色就是單線程,也就是說在一個時間段裏,JavaScript只能作一件事情(瀏覽器是多線程)。 多線程能夠實現應用的並行處理,從而以更高的CPU利用率提升整個應用程序的性能和吞吐量web

可是JavaScript卻以單線程進行,爲何呢?數據庫

JavaScript是瀏覽器腳本語言,用於與用戶交互以及操做DOM。 考慮以下狀況,若是有兩個併發的操做,對同一個DOM節點分別進行刪除和修改樣式,此時瀏覽器就沒法決定到底採用哪一個線程的操做。相似數據庫,咱們能夠採用「鎖」來處理併發,可是這會平添複雜度。因此,JavaScript語言沒有支持多線程操做。 那又考慮這種狀況,既然JavaScript是單線程,在某一時刻內只能執行特定的一個任務,而且會阻塞其它任務執行。那麼若是用戶觸發了一個很是耗時的I/O操做,那麼按道理後續的全部操做都得等到I/O操做完成後方可進行。可是,事實上,後續的任務沒必要等待這個耗時的I/O操做完成,緣由就是JavaScript與生俱來的異步和回調api

而這背後剛好就是本文的主題——————事件循環數組

定義

事件循環包含了至少兩個任務隊列,宏任務隊列和微任務隊列。promise

宏任務

宏任務包含建立文檔對象、解析HTML、執行主線JavaScript代碼、更改當前URL以及各類事件,例如頁面加載、輸入、網絡事件和定時器等等。宏任務運行完成後,瀏覽器繼續其餘的任務調度,如從新渲染頁面或者垃圾回收。瀏覽器

微任務

微任務包括promise、回調函數、DOM發生變化等。微任務更新應用程序的狀態,必須在瀏覽器任務繼續執行其餘任務(渲染UI視圖或者進行下一個宏任務)以前執行。網絡

兩個基本原則

  1. 一次處理一個任務
  2. 一個任務開始直到運行完成,不會被其餘任務中斷

EC06193617CCE1FF11A9C3C68CF8DDF3.png
不管是宏任務隊列仍是微任務隊列,兩者在同一時刻都只執行一個任務,不過兩者也有重要的區別: 在一次循環中,最多處理一個宏任務,而微任務隊列中全部的微任務都會被處理

在微任務隊列清空後,事件循環會檢查當前是否須要從新渲染UI,若是須要則渲染UI視圖。多線程

補充

  1. 兩個任務隊列都是獨立於事件循環的,這意味着任務隊列的添加發生在事件循環外。
  2. 全部微任務都會在下一次渲染前完成,目的是在渲染前更新應用程序狀態。
  3. 瀏覽器會嘗試以每秒渲染60次頁面,以達到每秒60幀的速度。因此,一次循環最理想的時間應該不超過16ms。
  4. 瀏覽器完成頁面渲染後,進入下一輪事件循環迭代後,可能出現3種狀況
    1. 若是事件循環執行到「is rendering needed」且瀏覽器處於另外一個16ms結束以前(即瀏覽器還沒有自動觸發頁面渲染時),瀏覽器可能不會選擇在當前的時間循環中執行更新UI操做,由於更新UI是一個複雜且耗性能的操做。
    2. 若是事件循環執行到「is rendering needed」且瀏覽器恰好離上一次渲染16ms左右時(即瀏覽器即將自動觸發頁面渲染時),此時瀏覽器會進行UI更新。
    3. 執行下一個事件循環耗時超過16ms,瀏覽器將沒法以目標幀率從新渲染頁面,且UI沒法被更新。若是延遲不大是很難察覺到,可是,若是有很是耗時的操做,這個時候用戶會以爲網頁十分卡頓,甚至瀏覽器會提示「無響應腳本」。

前情回顧

如今,用事件循環和簡單的例子來分析《高性能的JavaScript》中的那句話。 需求:給包含1000個數字的數組中的每一個元素取絕對值(假設對一個數字進行需求操做耗時1ms)。

狀況1(不使用定時器): 因爲JavaScript主線程代碼屬於宏任務的一種,因此一次事件循環須要處理1000個數字,因此1s事件循環才進行到UI更新階段,可是因爲耗時過長,UI狀態不會被更新,頁面出現卡頓甚至堵塞。

狀況2(使用定時器): 將一次處理1000個數字的任務分割爲20個每次處理50個數字的任務。因爲定時器是宏任務的一種,因此一次事件循環只處理50個數字,因爲此時微任務隊列爲空,因此50ms後事件循環進行到UI更新階段,而後根據狀況進行UI渲染,頁面未出現卡頓或者堵塞。

固然,若是隻是單純的處理數據,咱們能夠考慮使用Web Workers

總結

  1. JavaScript是單線程的,同一時刻是隻能執行一個任務。
  2. 事件循環包含一個宏任務隊列和至少一個微任務隊列。事件循環一次迭代,至多執行一個宏任務可是會執行完全部的微任務。
  3. Web應用越複雜,積極主動管理UI線程就越重要,即便JavaScript代碼很重要,也不能影響用戶體驗。

參考

  1. 《High Performance JavaScript》
  2. 《Secrets of the JavaScript Ninja》
  3. 《HTML 5.2》
相關文章
相關標籤/搜索