翻譯自:How JavaScript works: the rendering engine and tips to optimize its performancejavascript
這是探索 JavaScript 及其構建組件專題系列的第 11 篇。在識別和描述核心元素的過程當中,咱們分享了在構建 SessionStack 時使用的一些經驗法則。SessionStack 是一個須要魯棒且高性能的 JavaScript 應用程序,它幫助用戶實時查看和重現它們 Web 應用程序的缺陷。css
當構建 Web 應用程序時,你不僅是編寫獨立運行的 JavaScript 代碼片斷。你編寫的 JavaScript 須要與環境進行交互。理解環境是如何工做的以及它是由什麼組成的,你就可以構建更好的應用程序,而且能更好地處理應用程序發佈後纔會顯現的潛在問題。html
那麼,讓咱們看看瀏覽器的主要組件有哪些:html5
在這篇文章中,咱們將關注渲染引擎,由於它負責處理 HTML 和 CSS 的解析和可視化,這是大多數 JavaScript 應用程序不斷與之交互的地方。java
渲染引擎的主要職責是在瀏覽器屏幕上顯示所請求的頁面。node
渲染引擎能夠顯示 HTML / XML 文檔和圖像。若是你使用其餘插件,它還能夠顯示不一樣類型的文檔,例如 PDF。git
與 JavaScript 引擎相似,不一樣的瀏覽器也使用不一樣的渲染引擎。常見的有這些:github
渲染引擎從網絡層接收所請求文檔的內容。web
渲染引擎的第一步是解析 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 指 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 樹的渲染器樹的樣子:
爲了構建渲染樹,瀏覽器大體作了以下工做:
display: none
屬性。你能夠在這裏查看 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 添加了一個選項,能夠將腳本標記爲異步,此時腳本被其餘線程解析和執行。
若是你想優化你的應用,那麼你須要關注五個主要方面。這些是您能夠控制的地方:
<body>
的寬度會影響子元素的寬度等等。這一切都意味着佈局過程是計算密集型的。該繪圖是在多個圖層完成的。
JavaScript 常常觸發瀏覽器中的視覺變化,構建 SPA 時更是如此。
如下是關於能夠優化 JavaScript 哪些部分來改善渲染性能的一些小提示:
setTimeout
或 setInterval
進行視圖更新。這些將在幀中某個不肯定的時間點上調用 callback
,可能在最後。咱們想要作的是在幀開始時觸發視覺變化而不是錯過它。requestAnimationFrame
、setTimeout
或 setInterval
中運行它們。
經過添加和刪除元素、更改屬性等來修改 DOM 會致使瀏覽器從新計算元素樣式,而且在不少狀況下還會從新佈局整個頁面或至少其中的一部分。
要優化渲染性能,請考慮如下方法:
佈局的從新計算會對瀏覽器形成很大壓力。請考慮下面的優化:
flexbox
而不是老的佈局模型。它運行速度更快,可爲你的應用程序創造巨大的性能優點。box.offsetHeight
是沒問題的。 可是,若是你在查詢元素以前更改了元素的樣式(例如,動態向元素添加一些 CSS 類),瀏覽器必須先應用樣式更改並執行佈局過程。這可能很是耗時且耗費資源,所以請儘量避免。
優化繪圖
這一般是全部任務中運行時間最長的,所以儘量避免這種狀況很是重要。 如下是咱們能夠作的事情:
渲染是 SessionStack 運行的重點之一。當用戶瀏覽你的 web 應用遇到問題時,SessionStack 必須將這些遇到的問題重建成一個視頻。爲了作到這點,SessionStack 僅利用咱們的庫收集到數據:用戶事件、DOM 更改、網絡請求、異常和調試消息等。咱們的播放器通過高度優化,可以按順序正確呈現和使用全部收集到的數據,從視覺和技術兩方面爲你提供用戶在瀏覽器中發生的一切的像素級完美模擬。
若是你想試試看,這裏能夠免費嘗試 SessionStack。
本文來自網易雲社區,經做者徐子航受權發佈。
原文地址:JavaScript 如何工做:渲染引擎和性能優化技巧
更多網易研發、產品、運營經驗分享請訪問網易雲社區。