[譯] JavaScript 工做原理:渲染引擎及其性能優化

好久沒有翻譯文章了,最近看到一篇不錯的文章,恰好安排上。javascript

原文地址:How JavaScript works: the rendering engine and tips to optimize its performancecss

JavaScript 工做原理:渲染引擎及其性能優化

這是探討 JavaScript 系列文章的第 11 篇,專門探討 JavaScript 及其構建組件。識別和描述核心元素的過程當中,咱們還分享了一些在構建 SessionStack 時使用的經驗法則。SessionStack 是一個須要強大且高性能的 JavaScript 應用程序,以幫助用戶實時發現並重現他們的 Web 應用程序缺陷。html

若是你想閱讀系列的其餘文章,能夠看下面的連接:html5

到目前爲止,在咱們以前的 "JavaScript的工做原理" 系列博客文章中,咱們一直專一於JavaScript做爲一種語言,其功能,如何在瀏覽器中執行,如何對其進行優化等。java

可是,在構建 Web 應用程序時,不只僅只是會編寫獨立運行的 JavaScript 代碼。您編寫的 JavaScript 代碼須要和環境進行交互。瞭解此環境,和其工做方式以及它的組成將使您可以構建更好的 web 應用程序,並能爲後續應用程序發佈後可能出現的潛在問題作好充分的準備。 node

那麼,咱們來看看瀏覽器主要由哪些部分組成:git

  • 用戶界面:包括地址欄、後退和前進按鈕、書籤菜單等等。大致上,這是除了瀏覽網頁的窗口之外瀏覽器所展現的部分。
  • 瀏覽器引擎:它處理用戶界面和渲染引擎之間的交互。
  • 渲染引擎:它負責顯示網頁。渲染引擎解析 HTML 和 CSS,並在屏幕上顯示解析後的內容。
  • 網絡層:這些是不一樣的平臺使用不一樣的方式實現的網絡調用,如 XHR 請求,位於獨立於平臺的接口後面。 在本系列以前的文章中,咱們更詳細地討論了網絡層。
  • UI 後臺:它用於繪製核心小部件,如複選框和窗口。該後臺暴露了一個非平臺特定的通用接口。它使用操做系統底層的 UI 方法。
  • JavaScript 引擎:在本系列以前的文章中,咱們已進行了詳細的介紹。大致就是,這是 JavaScript 執行的地方。
  • 數據持久化:你的應用程序可能須要在本地存儲全部數據。支持的存儲機制類型包括 localStorageindexDBWebSQLFileSystem

本文中,咱們將重點介紹渲染引擎,由於它處理 HTML 和 CSS 的解析和可視化,並且大多數 JavaScript 應用程序會一直與之交互。github

渲染引擎概述

渲染引擎主要負責在瀏覽器屏幕上顯示請求的頁面。web

渲染引擎能夠顯示 HTML 和 XML 文件和圖像。若是你使用其餘的插件,該引擎還能顯示不一樣類型的文檔,如 PDF。編程

渲染引擎

與 JavaScript 引擎相似,不一樣的瀏覽器也使用不一樣的渲染引擎。下面是一些流行的渲染引擎:

  • Gecko — Firefox
  • WebKit — Safari
  • Blink — Chrome, Opera (從版本15起)

渲染過程

渲染引擎從網絡層接收所請求文檔的內容。

構造DOM樹

渲染引擎的第一步是解析 HTML 文檔,並將解析的元素轉換爲DOM 樹 中的實際 DOM 節點。

假設你輸入瞭如下文本:

<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="theme.css">
  </head>
  <body>
    <p> Hello, <span> friend! </span> </p>
    <div> 
      <img src="smiley.gif" alt="Smiley face" height="42" width="42">
    </div>
  </body>
</html>
複製代碼

該 HTML 文檔的 DOM 樹看起來像下面這樣:

基本上,每一個元素都表示爲其直接所包含的全部元素的父節點,而且這是遞歸進行的。

構造 CSSOM 樹

CSSOM 指的是 CSS 對象模型。當瀏覽器構建頁面的 DOM 時,在 head 部分,遇到引用外部層疊樣式表 theme.csslink 標籤時,預計可能須要這個資源來渲染頁面,它就當即爲此發送了一個請求。 假設 theme.css 文件包含如下內容:

body { 
  font-size: 16px;
}

p { 
  font-weight: bold; 
}

span { 
  color: red; 
}

p span { 
  display: none; 
}

img { 
  float: right; 
}
複製代碼

和 HTML 同樣,引擎須要將 CSS 轉換成瀏覽器能夠識別的東西— CSSOM。 下面是 CSSOM 樹的樣子:

你想知道爲何 CSSOM 有樹形結構嗎?當計算頁面上任何對象的最終樣式集時,瀏覽器從適用於該節點的最通常規則(例如,若是它是 body 元素的子級,則應用全部 body 的樣式)開始,而後經過應用更具體的規則遞歸地優化計算出的樣式。

讓咱們來研究一下給出的具體示例。 位於 body 元素中的 span 標籤所包含的全部文本,字體大小都是 16px 而且顏色爲紅色。這些樣式是從 body 元素繼承的。 若是 span 元素是 p 標籤的子元素,則會由於設置了更具體的樣式,而不會顯示其內容。

另外,請注意,上面的樹不是完整的 CSSOM 樹,它只顯示了樣式表中咱們決定重寫的樣式。每一個瀏覽器都提供了一組默認的樣式,也稱爲 用戶代理樣式 — 也就是咱們沒有明確提供任何樣式時會看到的樣式。 咱們的樣式只是覆蓋了這些默認樣式。

構建渲染樹

HTML 中的視覺指令與 CSSOM 樹中的樣式數據相結合,用於建立渲染樹。

你可能會問什麼是渲染樹?這是一個可視化元素樹,按照它們在屏幕上顯示的順序構建。它是 HTML 和相應的 CSS 的可視化表示。此樹的目的是使內容可以按正確的順序進行繪製。

渲染樹中的每一個節點稱爲渲染器或WebKit中的渲染對象。

這裏是上面的 DOM 和 CSSOM 樹的渲染器樹的樣子:

要構造渲染樹,瀏覽器大體執行如下操做:

  • 從 DOM 樹的根節點開始,它遍歷每一個可見節點。某些節點是不可見的(例如,script 標籤,meta 標籤等等),而且因爲它們不進行渲染輸出而被忽略。還有些經過 CSS 隱藏的節點,也會從渲染樹中省略。例如,上例中的 span 標籤 — 在渲染樹中不存在,由於咱們對其設置了 display:none 屬性。
  • 對每一個可見節點,瀏覽器會找到合適的匹配 CSSOM 規則並應用它們。
  • 它發出帶有內容及其計算樣式的可見節點。

你能夠在這裏查看 RenderObject 的源代碼(在webkit中):github.com/WebKit/webk…

讓咱們來看看這個類的核心內容:

class RenderObject : public CachedImageClient {
  // Repaint the entire object.  Called when, e.g., the color of a border changes, or when a border
  // style changes.

  Node* node() const { ... }

  RenderStyle* style;  // the computed style
  const RenderStyle& style() const;

  ...
}
複製代碼

每一個渲染器表明一個一般與某節點的 CSS 盒子相對應的矩形區域。它包含了一些幾何信息,如寬,高和位置。

渲染樹的佈局

當渲染器被建立並添加到樹中時,它是沒有位置和大小的。而計算這些值就稱爲佈局。

HTML 使用基於流的佈局模型,這意味着它大多數狀況下能夠一次性計算出幾何結構。座標系是相對於根渲染器的,使用頂部和左側座標。

佈局是一個遞歸的過程 — 它從對應於 HTML 文檔 <html> 元素的根渲染器開始。佈局經過部分或整個渲染器的層級繼續遞歸,爲每一個依賴它的渲染器計算幾何信息。

根渲染器的位置爲0,0,其尺寸與瀏覽器窗口(即視區)的可見部分大小相同。

佈局過程的開始意味着給每一個節點賦予應該在屏幕中顯示的精確座標。

繪製渲染樹

在此階段,將遍歷渲染器樹,並調用渲染器的 paint() 方法在屏幕上顯示內容。

繪製能夠是全局的或遞增的(相似於佈局):

  • 全局 - 從新繪製整個樹。
  • 遞增 - 只有部分渲染器以不會影響整個樹的方式改變。渲染器使屏幕上的矩形無效。這將致使操做系統將其視爲須要重繪的區域並生成一個 paint 事件。操做系統經過一種將多個區域合併爲一個區域的智能方式來實現這一點。

一般來講,瞭解繪製是一個漸進的過程是很是重要的。爲了更好的用戶體驗,渲染引擎會盡量快地將內容顯示在屏幕上。它不會等到全部的 HTML 都被解析後纔開始構建和繪製渲染樹。部份內容將被解析並顯示,同時該過程會繼續處理來自網絡的其他內容項。

處理腳本和樣式表的順序

當解析器到達 <script> 標籤時,將當即解析並執行腳本。在腳本執行以前,將中止對文檔的解析,這意味着該過程是同步的。

若是腳本是外部的,那麼首先必須從網絡中獲取它(也是同步的)。在獲取完成以前,全部解析都將中止。

HTML5 添加了一個選項,將腳本標記爲異步腳本,以便由其餘線程解析和執行。

優化渲染性能

若是你想優化你的應用程序,你須要關注五個主要方面。如下是你能夠控制的方面:

  • 1.JavaScript — 在以前的文章中,咱們討論了編寫優化代碼的主題,優化的代碼不會阻塞 UI,內存效率高等等。當涉及到渲染時,咱們須要考慮 JavaScript 代碼與頁面上的 DOM 元素交互的方式。JavaScript 能夠在 UI 中建立大量的更改,特別是在單頁應用中。
  • 2.樣式計算 - 這是根據匹配的選擇器肯定哪一個 CSS 規則應用於哪一個元素的過程。一旦規則被定義,將應用這些規則並計算每一個元素的最終樣式。
  • 3.佈局 - 一旦瀏覽器知道哪些規則應用於某個元素,它就能夠開始計算元素佔用的空間以及它在瀏覽器屏幕上的位置。web 的佈局模型定義了一個元素能夠影響其餘元素。例如, 的寬度能夠影響其子級的寬度等。這意味着佈局過程是計算密集型的而且繪製是在多層上完成的。
  • 4.繪製 - 這是填充實際像素的步驟。這個過程包括繪製文本、顏色、圖像、邊框、陰影等 — 每一個元素的每一個可視部分。
  • 5.合成 - 因爲頁面部分被繪製到潛在的多個層中,因此它們須要以正確的順序繪製到屏幕上,以便頁面正確渲染。這一點很是重要,特別是對於重疊的元素。

優化 JavaScript

JavaScript 常常觸發瀏覽器中的視覺變化。尤爲是構建單頁應用時。

下面是一些關於你能夠優化 JavaScript 的哪些部分來改進渲染的建議:

  • 避免使用 setTimeoutsetInterval 進行視覺更新。它們將在幀的某個不肯定時刻調用 「callback」,可能在幀結束時執行。咱們要作的是在幀開始時觸發視覺變化而不要錯過它。
  • 將長時間運行的 JavaScript 計算交給 Web Workers 處理,就像咱們以前討論的同樣。
  • 使用微任務進行多幀的 DOM 更新。這是爲了防止任務須要訪問 DOM,而 Web Workers 沒法訪問 DOM。這基本上意味着你能夠將一個大任務分解成更小的任務,並根據任務的性質在 requestAnimationFramesetTimeoutsetInterval 中運行它們。

優化 CSS

經過添加和刪除元素,修改屬性等更改 DOM 將使瀏覽器從新計算元素樣式,在許多狀況下,還會從新計算整個頁面或至少部分頁面的佈局。

要優化渲染,請考慮如下內容:

  • 減小選擇器的複雜性。與構建樣式自己的其餘工做相比,選擇器複雜性能佔用計算元素樣式所需的50%以上的時間。
  • 減小必須對其進行樣式計算的元素的數量。大致上就是直接對一些元素進行樣式更改,而不是使整個頁面失效。

優化佈局

佈局的從新計算對於瀏覽器來講可能很是繁重。能夠考慮如下方面的優化:

  • 儘量減小布局的數量。更改樣式時,瀏覽器將檢查是否有任何更改須要從新計算佈局。對 width,height,left,top 等屬性以及一般與幾何圖形相關的屬性的更改,都須要佈局。因此,儘可能避免改變這些屬性。
  • 儘可能在舊的佈局模型上使用 flexbox。它渲染得更快,能夠爲你的應用程序創造巨大的性能優點。
  • 避免進行強制的同步佈局。須要記住的是,當 JavaScript 運行時,來自前一幀的全部舊的佈局值都是已知且能獲取的。因此,若是你獲取 box.offthesight 是沒有問題的。可是,若是在獲取前更改了盒子的樣式(例如,給元素動態添加一些 CSS 類),瀏覽器必須先去更改樣式,而後再進行佈局。這可能很是耗費時間和資源,所以要儘量避免。

優化繪製

繪製一般是全部任務中運行時間最長的,所以儘量避免繪製是很是重要的。咱們能夠這樣作:

  • 更改除了 transforms 或 opacity 之外的任何屬性都會觸發繪製。請謹慎使用。
  • 只要觸發佈局就會觸發繪製,由於更改幾何結構會致使元素的視覺的更改。
  • 經過圖層優化和動畫編排減小繪製區域。

渲染是 SessionStack 正常運轉的一個重要方面。SessionStack 必須將用戶在瀏覽 Web 應用程序時遇到問題時發生的全部事情從新建立成一個視頻。爲此,SessionStack 僅利用咱們的庫收集的數據:用戶事件、DOM更改、網絡請求、異常、調試消息等。咱們的播放器通過高度優化,可以正確地渲染和使用全部收集的數據,以便不管從視覺上仍是技術上爲用戶的瀏覽器和瀏覽器中發生的一切提供一個完美像素模擬。

若是你想 試一試 SessionStack,有一個免費的計劃。

參考資源

相關文章
相關標籤/搜索