1. 程式人生 > >JavaScript 如何工作:渲染引擎和性能優化技巧

JavaScript 如何工作:渲染引擎和性能優化技巧

blob 默認值 書簽 隱藏 播放 wikipedia 強制 之前 blocks

翻譯自:How JavaScript works: the rendering engine and tips to optimize its performance

這是探索 JavaScript 及其構建組件專題系列的第 11 篇。在識別和描述核心元素的過程中,我們分享了在構建 SessionStack 時使用的一些經驗法則。SessionStack 是一個需要魯棒且高性能的 JavaScript 應用程序,它幫助用戶實時查看和重現它們 Web 應用程序的缺陷。

當構建 Web 應用程序時,你不只是編寫獨立運行的 JavaScript 代碼片段。你編寫的 JavaScript 需要與環境進行交互。理解環境是如何工作的以及它是由什麽組成的,你就能夠構建更好的應用程序,並且能更好地處理應用程序發布後才會顯現的潛在問題。

技術分享圖片

那麽,讓我們看看瀏覽器的主要組件有哪些:

  • 用戶界面:包括地址欄、後退和前進按鈕、書簽菜單等。實際上,它包括了瀏覽器中顯示的絕大部分,除了你看到的網頁本身的那個窗口。
  • 瀏覽器引擎:它處理用戶界面和渲染引擎之間的交互。
  • 渲染引擎:它負責顯示網頁。渲染引擎解析 HTML 和 CSS,並在屏幕上顯示解析的內容。
  • 網絡層:諸如 XHR 請求之類的網絡調用,通過對不同平臺的不同的實現來完成,這些實現位於一個平臺無關的接口之後。我們在本系列的上一篇文章中更詳細地討論了網絡層。
  • UI 後端:它用於繪制核心組件(widget),例如復選框和窗口。這個後端暴露了一個平臺無關的通用接口。它使用下層的操作系統提供的 UI 方法。
  • JavaScript 引擎:我們在上一篇文章中詳細介紹了這一主題。基本上,這是 JavaScript 執行的地方。
  • 數據持久化層:你的應用可能需要在本地存儲所有數據。其支持的存儲機制包括 localStorageindexDBWebSQLFileSystem

在這篇文章中,我們將關註渲染引擎,因為它負責處理 HTML 和 CSS 的解析和可視化,這是大多數 JavaScript 應用程序不斷與之交互的地方。

渲染引擎概述

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

渲染引擎可以顯示 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.css CSS 樣式表的 link 標簽。瀏覽器預計到它可能需要該資源來呈現頁面,所以它立即發出請求。讓我們假設 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 標簽中的任何文本的字體大小均為 16 像素,並且為紅色。這些樣式是從 body 元素繼承而來的。 如果一個 span 元素是一個 p 元素的子元素,那麽它的內容就不會被顯示,因為它被應用了更具體的樣式(display: none)。

另外請註意,上面的樹不是完整的 CSSOM 樹,只顯示了我們決定在樣式表中重寫的樣式。每個瀏覽器都提供了一組默認的樣式,也稱為 「用戶代理樣式」——這是我們在未明確指定任何樣式時看到的樣式。我們的樣式會覆蓋這些默認值。

構建渲染樹

HTML 中的視圖指令與 CSSOM 樹中的樣式數據結合在一起用來創建渲染樹

你可能會問什麽是渲染樹。渲染樹是一顆由可視化元素以它們在屏幕上顯示的順序而構成的樹型結構。它是 HTML 和相應的 CSS 的可視化表示。此樹的目的是為了以正確的順序繪制內容。

渲染樹中的節點被稱為 Webkit 中的渲染器或渲染對象。

這就是上述 DOM 和 CSSOM 樹的渲染器樹的樣子:

技術分享圖片

為了構建渲染樹,瀏覽器大致做了如下工作:

  • 從 DOM 樹的根開始,瀏覽器遍歷每個可見節點。某些節點是不可見的(例如 script、meta 等),並且由於它們不需要渲染而被忽略。一些通過 CSS 隱藏的節點也從渲染樹中省略。例如 span 節點 —— 在上面的例子中,它並不存在於渲染樹中,因為我們明確地其上設置了 display: none 屬性。
  • 對於每個可見節點,瀏覽器找到適當的 CSSOM 規則並應用它們。
  • 瀏覽器輸出帶有內容及其計算出的樣式的可見節點

你可以在這裏查看 RenderObject 的源代碼(在 WebKit 中):https://github.com/WebKit/webkit/blob/fde57e46b1f8d7dde4b2006aaf7ebe5a09a6984b/Source/WebCore/rendering/RenderObject.h

我們來看看這個類的一些核心內容:

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 使用基於流的布局模型,這意味著大部分時間內它可以在一次遍歷中(single pass)計算出布局。坐標系是相對於根渲染器的,使用左上原點坐標。

布局是一個遞歸過程 —— 它從根渲染器開始,對應於 HTML 文檔的 <html> 元素,通過部分或整個渲染器的層次結構遞歸地為每個需要布局的渲染器計算布局信息。

根渲染器的位置是 0,0,並且其尺寸為瀏覽器窗口(也稱為視口)的可見部分的尺寸。

開始布局過程意味著給出每個節點它應該出現在屏幕上的確切坐標。

繪制渲染樹

在這個階段,瀏覽器遍歷渲染器樹,調用渲染器的 paint() 方法在屏幕上顯示內容。

繪圖可以是全局的或增量式的(與布局類似):

  • 全局 —— 整棵樹被重畫
  • 增量式 —— 只有一些渲染器以不影響整個樹的方式進行變更。渲染器在屏幕上標記其矩形區域無效,這會導致操作系統將其視為需要重繪並生成 paint 事件的區域。操作系統通過將幾個區域合並為一個區域的智能方式來完成繪圖。

一般來說,了解繪圖是一個漸進的過程是很重要的。為了更好的用戶體驗,渲染引擎會嘗試盡快在屏幕上顯示內容。它不會等到所有的 HTML 被分析完畢才開始構建和布置渲染樹。一小部分內容先被解析並顯示,同時一邊從網絡獲取剩下的內容一邊漸進地渲染。

處理腳本和樣式表的順序

當解析器到達 <script> 標簽時,腳本將被立即解析並執行。文檔解析將會被暫停,直到腳本執行完畢。這意味著該過程是同步的。

如果腳本是外部的,那麽它首先必須從網絡獲取(也是同步的)。所有解析都會停止,直到網絡請求完成。

HTML5 添加了一個選項,可以將腳本標記為異步,此時腳本被其他線程解析和執行。

優化渲染性能

如果你想優化你的應用,那麽你需要關註五個主要方面。這些是您可以控制的地方:

  1. JavaScript —— 在之前的文章中,我們介紹了關於編寫高性能代碼的主題,這些代碼不會阻塞 UI,並且內存效率高等等。當涉及渲染時,我們需要考慮 JavaScript 代碼與頁面上 DOM 元素交互的方式。JavaScript 可以在 UI 中產生大量的更新,尤其是在 SPA 中。
  2. 樣式計算 —— 這是基於匹配選擇器確定哪個 CSS 規則適用於哪個元素的過程。一旦定義了規則,就會應用這些規則,並計算出每個元素的最終樣式。
  3. 布局 —— 一旦瀏覽器知道哪些規則適用於元素,就可以開始計算後者占用的空間以及它在瀏覽器屏幕上的位置。Web 的布局模型定義了一個元素可以影響其他元素。例如,<body> 的寬度會影響子元素的寬度等等。這一切都意味著布局過程是計算密集型的。該繪圖是在多個圖層完成的。
  4. 繪圖 —— 這裏開始填充實際的像素。該過程包括繪制文本、顏色、圖像、邊框、陰影等 —— 每個元素的每個視覺部分。
  5. 合成 —— 由於頁面部件被劃分為多層,因此需要按照正確的順序將其繪制到屏幕上,以便正確地渲染頁面。這非常重要,特別是對於重疊元素來說。

優化你的 JavaScript

JavaScript 經常觸發瀏覽器中的視覺變化,構建 SPA 時更是如此。

以下是關於可以優化 JavaScript 哪些部分來改善渲染性能的一些小提示:

  • 避免使用 setTimeoutsetInterval 進行視圖更新。這些將在幀中某個不確定的時間點上調用 callback,可能在最後。我們想要做的是在幀開始時觸發視覺變化而不是錯過它。
  • 將長時間運行的 JavaScript 計算任務移到 Web Workers 上,像我們之前討論過的那樣
  • 使用微任務在多個幀中變更 DOM。這是為了處理在 Web Worker 中的任務需要訪問 DOM,而 Web Worker 又不允許訪問 DOM 的情況。就是說你可以將一個大任務分解為小任務,並根據任務的性質在 requestAnimationFramesetTimeoutsetInterval 中運行它們。

優化你的 CSS

通過添加和刪除元素、更改屬性等來修改 DOM 會導致瀏覽器重新計算元素樣式,並且在很多情況下還會重新布局整個頁面或至少其中的一部分。

要優化渲染性能,請考慮以下方法:

  • 減少選擇器的復雜性。相對於構建樣式本身的工作,復雜的選擇器可能會讓計算元素樣式所需的時間增加 50%。
  • 減少必須計算樣式的元素的數量。本質上,直接對幾個元素進行樣式更改,而不是使整個頁面無效。

優化布局

布局的重新計算會對瀏覽器造成很大壓力。請考慮下面的優化:

  • 盡可能減少布局的數量。當你更改樣式時,瀏覽器將檢查是否需要重新計算布局。對屬性的更改,如寬度、高度、左、上和其他與幾何有關的屬性,都需要重新布局。所以,盡量避免改變它們。
  • 盡量使用 flexbox 而不是老的布局模型。它運行速度更快,可為你的應用程序創造巨大的性能優勢。
  • 避免強制同步布局。需要註意的是,在 JavaScript 運行時,前一幀中的所有舊布局值都是已知的並且可以查詢。如果你查詢 box.offsetHeight 是沒問題的。 但是,如果你在查詢元素之前更改了元素的樣式(例如,動態向元素添加一些 CSS 類),瀏覽器必須先應用樣式更改並執行布局過程。這可能非常耗時且耗費資源,因此請盡可能避免。

優化繪圖

這通常是所有任務中運行時間最長的,因此盡可能避免這種情況非常重要。 以下是我們可以做的事情:

  • 除了變換(transform)和透明度之外,改變其他任何屬性都會觸發重新繪圖,請謹慎使用。
  • 如果觸發了布局,那也會觸發繪圖,因為更改布局會導致元素的視覺效果也改變。
  • 通過圖層提升和動畫編排來減少重繪區域。

渲染是 SessionStack 運行的重點之一。當用戶瀏覽你的 web 應用遇到問題時,SessionStack 必須將這些遇到的問題重建成一個視頻。為了做到這點,SessionStack 僅利用我們的庫收集到數據:用戶事件、DOM 更改、網絡請求、異常和調試消息等。我們的播放器經過高度優化,能夠按順序正確呈現和使用所有收集到的數據,從視覺和技術兩方面為你提供用戶在瀏覽器中發生的一切的像素級完美模擬。

如果你想試試看,這裏可以免費嘗試 SessionStack

技術分享圖片

資源

  • https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model
  • https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations
  • https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#The_parsing_algorithm

本文來自網易雲社區,經作者徐子航授權發布。

原文地址:JavaScript 如何工作:渲染引擎和性能優化技巧

更多網易研發、產品、運營經驗分享請訪問網易雲社區。

JavaScript 如何工作:渲染引擎和性能優化技巧