標籤:本地 width head 儲存機制 org and 情況下 多個 write
翻譯自: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 執行的地方。
- 資料持久化層:你的應用可能需要在本機存放區所有資料。其支援的儲存機制包括 localStorage、indexDB、WebSQL 和 FileSystem。
在這篇文章中,我們將關注渲染引擎,因為它負責處理 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 添加了一個選項,可以將指令碼標記為非同步,此時指令碼被其他線程解析和執行。
最佳化渲染效能
如果你想最佳化你的應用,那麼你需要關注五個主要方面。這些是您可以控制的地方:
- JavaScript —— 在之前的文章中,我們介紹了關於編寫高效能代碼的主題,這些代碼不會阻塞 UI,並且記憶體效率高等等。當涉及渲染時,我們需要考慮 JavaScript 代碼與頁面上 DOM 元素互動的方式。JavaScript 可以在 UI 中產生大量的更新,尤其是在 SPA 中。
- 樣式計算 —— 這是基於匹配選取器確定哪個 CSS 規則適用於哪個元素的過程。一旦定義了規則,就會應用這些規則,並計算出每個元素的最終樣式。
- 布局 —— 一旦瀏覽器知道哪些規則適用於元素,就可以開始計算後者佔用的空間以及它在瀏覽器螢幕上的位置。Web 的布局模型定義了一個元素可以影響其他元素。例如,
<body>
的寬度會影響子項目的寬度等等。這一切都意味著版面配置階段是計算密集型的。該繪圖是在多個圖層完成的。
- 繪圖 —— 這裡開始填充實際的像素。該過程包括繪製文本、顏色、映像、邊框、陰影等 —— 每個元素的每個視覺部分。
- 合成 —— 由於頁面組件被劃分為多層,因此需要按照正確的順序將其繪製到螢幕上,以便正確地渲染頁面。這非常重要,特別是對於重疊元素來說。
最佳化你的 JavaScript
JavaScript 經常觸發瀏覽器中的視覺變化,構建 SPA 時更是如此。
以下是關於可以最佳化 JavaScript 哪些部分來改善渲染效能的一些小提示:
- 避免使用
setTimeout
或 setInterval
進行視圖更新。這些將在幀中某個不確定的時間點上調用 callback
,可能在最後。我們想要做的是在幀開始時觸發視覺變化而不是錯過它。
- 將長時間啟動並執行 JavaScript 計算任務移到 Web Workers 上,像我們之前討論過的那樣
- 使用微任務在多個幀中變更 DOM。這是為了處理在 Web Worker 中的任務需要訪問 DOM,而 Web Worker 又不允許訪問 DOM 的情況。就是說你可以將一個大任務分解為小任務,並根據任務的性質在
requestAnimationFrame
、setTimeout
或 setInterval
中運行它們。
最佳化你的 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 如何工作:渲染引擎和效能最佳化技巧