轉譯器程序的內部運作方式
這是部落格系列文章的第 3 篇,共 4 篇,探討瀏覽器的運作方式。我們先前已介紹多程序架構和導覽流程。在本篇文章中,我們將探討轉譯器程序內發生的情況。
轉譯器程序會影響許多網頁效能層面。由於轉譯器處理程序內發生的事件很多,因此本篇文章僅提供概略的介紹。如要進一步瞭解,請參閱 Web 基礎知識的「成效」部分,這裡有更多資源。
轉譯器處理程序會處理網頁內容
算繪程序負責處理分頁中發生的所有事件。在轉譯器處理程序中,主執行緒會處理您傳送給使用者的大部分程式碼。如果您使用 Web Worker 或 Service Worker,部分 JavaScript 可能會由 worker 執行緒處理。合成器和光柵執行緒也會在轉譯器程序中執行,以便順利且有效率地轉譯網頁。
轉譯器程序的主要工作,就是將 HTML、CSS 和 JavaScript 轉換成使用者可互動的網頁。

剖析
DOM 的建構
當轉譯器程序收到導覽的提交訊息,並開始接收 HTML 資料時,主執行緒就會開始剖析文字字串 (HTML),並將其轉換為 Document Object Model (DOM)。
DOM 是瀏覽器對網頁的內部表示法,也是網頁開發人員可透過 JavaScript 互動的資料結構和 API。
將 HTML 文件剖析成 DOM 的作業是由 HTML 標準定義。您可能已經注意到,向瀏覽器提供 HTML 時不會擲回錯誤。舉例來說,缺少關閉 </p>
標記的 HTML 是有效的。Hi! <b>I'm <i>Chrome</b>!</i>
這類錯誤的標記 (b 標記在 i 標記之前關閉) 會視為您寫入 Hi! <b>I'm <i>Chrome</i></b><i>!</i>
。這是因為 HTML 規格旨在妥善處理這些錯誤。如果您想知道這些操作的執行方式,請參閱 HTML 規格中的「An introduction to error handling and strange cases in the parser」一節。
子資源載入
網站通常會使用圖片、CSS 和 JavaScript 等外部資源。這些檔案必須從網路或快取載入。主執行緒「可以」在剖析時逐一要求這些元素,以便建構 DOM,但為了加快速度,「預先載入掃描器」會並行執行。如果 HTML 文件中含有 <img>
或 <link>
之類的內容,預先載入掃描器會查看 HTML 剖析器產生的符記,並將要求傳送至瀏覽器程序中的網路執行緒。

JavaScript 可以封鎖剖析作業
HTML 剖析器找到 <script>
標記時,會暫停剖析 HTML 文件,並必須載入、剖析及執行 JavaScript 程式碼。原因是 JavaScript 可以使用 document.write()
之類的項目變更文件形狀,而 document.write()
會變更整個 DOM 結構 (HTML 規格中的剖析模型總覽有個不錯的圖表)。因此,HTML 剖析器必須等待 JavaScript 執行完畢,才能繼續剖析 HTML 文件。如果您想知道 JavaScript 執行程序會發生什麼事,請參閱 V8 團隊的演講和網誌文章。
向瀏覽器提供您想要載入資源的方式提示
網頁開發人員可以透過多種方式向瀏覽器傳送提示,以便順利載入資源。如果 JavaScript 未使用 document.write()
,您可以將 async
或 defer
屬性新增至 <script>
標記。接著,瀏覽器會以非同步方式載入及執行 JavaScript 程式碼,且不會阻擋剖析作業。您也可以使用JavaScript 模組 (如果適用的話)。<link rel="preload">
可用於通知瀏覽器,目前導覽作業確實需要這項資源,且您希望盡快下載。如要進一步瞭解這項功能,請參閱「資源優先順序 – 讓瀏覽器協助您」。
樣式計算
我們可以在 CSS 中為網頁元素設定樣式,因此光是使用 DOM 就不足以瞭解網頁的外觀。主執行緒會剖析 CSS,並判斷每個 DOM 節點的運算樣式。這項資訊是關於根據 CSS 選取器,將哪種樣式套用至每個元素。您可以在開發人員工具的 computed
專區中查看這項資訊。

即使您未提供任何 CSS,每個 DOM 節點都會有計算樣式。<h1>
標記會顯示比 <h2>
標記大,且系統會為每個元素定義邊界。這是因為瀏覽器有預設的樣式表單。如要瞭解 Chrome 的預設 CSS,請參閱這裡的原始碼。
版面配置
此時,轉譯器程序已知悉文件的結構和每個節點的樣式,但這還不足以轉譯網頁。假設您想透過手機向朋友描述一幅畫作。「畫面上有一個大紅圈和一個小藍方塊」這類資訊不足以讓朋友瞭解畫作實際外觀。

版面配置是尋找元素幾何圖形的程序。主執行緒會逐一檢查 DOM 和計算樣式,並建立版面配置樹狀結構,其中包含 x 和 y 座標和邊界框大小等資訊。版面配置樹狀結構的結構可能與 DOM 樹狀結構相似,但只包含與網頁上顯示內容相關的資訊。如果套用 display: none
,該元素就不會是版面配置樹狀結構的一部分 (不過,帶有 visibility: hidden
的元素會位於版面配置樹狀結構中)。同樣地,如果套用含有 p::before{content:"Hi!"}
等內容的擬似元素,即使該元素不在 DOM 中,也會納入版面配置樹狀結構。

決定網頁的版面配置是一項艱鉅的任務。即使是最簡單的頁面版面配置,例如從上到下的區塊流程,也必須考量字型大小和換行位置,因為這些因素會影響段落的大小和形狀,進而影響後續段落的位置。
CSS 可讓元素浮動到某一側、遮蔽溢位項目,以及變更書寫方向。您可以想像,這個版面配置階段有著艱鉅的任務。在 Chrome 中,整個工程師團隊都會著手處理版面配置。如要查看他們的工作詳細資料,請觀看 BlinkOn 大會的幾場演講錄影,內容相當有趣。
油漆

有了 DOM、樣式和版面配置,仍不足以轉譯網頁。假設您要複製一幅畫作,您知道元素的大小、形狀和位置,但仍須判斷繪製順序。
舉例來說,z-index
可能會針對特定元素進行設定,在這種情況下,依 HTML 中所寫元素的順序進行繪製,會導致算繪不正確。

在這個繪製步驟中,主執行緒會逐一檢查版面配置樹狀結構,以建立繪製記錄。繪圖記錄是繪圖程序的備註,例如「先繪製背景、再繪製文字、再繪製矩形」。如果您曾使用 JavaScript 在 <canvas>
元素上繪製圖形,這個程序可能會讓您感到熟悉。

更新算繪管道成本高昂
在轉譯管道中,最重要的是瞭解在每個步驟中,前一個作業的結果會用於建立新資料。舉例來說,如果版面配置樹狀結構發生變更,則需要針對文件的受影響部分重新產生繪圖順序。
如果您要為元素製作動畫,瀏覽器必須在每個影格之間執行這些作業。我們的大部分螢幕每秒會刷新 60 次 (60 fps);如果您在每個影格中在畫面上移動物件,動畫就會以流暢的速度呈現。不過,如果動畫錯過中間的畫格,網頁就會看起來「不自然」。

即使算繪作業能跟上螢幕刷新作業,這些計算仍會在主執行緒上執行,這表示在應用程式執行 JavaScript 時,可能會遭到封鎖。

您可以將 JavaScript 作業分割成小片段,並使用 requestAnimationFrame()
排程,以便在每個影格執行。如要進一步瞭解這個主題,請參閱「最佳化 JavaScript 執行作業」。您也可以在 Web Workers 中執行 JavaScript,避免封鎖主執行緒。

合成
你會如何繪製網頁?
瀏覽器現在已知文件結構、每個元素的樣式、網頁的幾何圖形和繪製順序,那麼它會如何繪製網頁?將這項資訊轉換為螢幕上的像素稱為「轉柵」。
處理這種情況的簡單方法,或許是將可視區域內的部分內容轉為點陣圖。如果使用者捲動頁面,則會移動已經過掃描的框架,並透過更多掃描作業填入缺少的部分。這是 Chrome 在首次發布時處理算繪的方式。不過,現代瀏覽器會執行更複雜的程序,稱為合成。
什麼是合成
合成是一種將網頁的部分內容分割成多個圖層,並分別將其轉為點陣圖,然後在稱為合成器執行緒的個別執行緒中合成為網頁的技術。如果發生捲動,由於圖層已經過算繪,因此只需合成新影格即可。動畫的製作方式也相同,只要移動圖層並合成新影格即可。
您可以使用「Layers」面板,查看網站如何在開發人員工具中劃分為多個圖層。
將圖層分割
為了找出哪些元素需要位於哪些圖層,主執行緒會逐一檢查版面配置樹狀結構,以建立圖層樹狀結構 (這個部分在 DevTools 效能面板中稱為「Update Layer Tree」)。如果網頁的某些部分應為獨立層級 (例如滑入式側邊選單),但並未獲得獨立層級,您可以在 CSS 中使用 will-change
屬性,向瀏覽器提供提示。

您可能會想為每個元素提供圖層,但在過多圖層上進行合成,可能會導致操作速度變慢,比起在每個影格中將網頁的某些小部分轉為點陣圖,更不利於效能,因此評估應用程式的算繪效能至關重要。如要進一步瞭解這個主題,請參閱「只使用合成器專屬屬性和管理圖層數量」。
將光柵和合成作業移出主執行緒
建立圖層樹狀結構並決定繪製順序後,主執行緒會將該資訊提交至合成器執行緒。接著,合成器執行緒會將每個圖層轉為點陣圖。圖層可能很大,例如整個網頁的長度,因此合成器執行緒會將圖層分割成圖塊,並將每個圖塊傳送至光柵執行緒。光柵執行緒會將每個圖塊轉為光柵,並儲存在 GPU 記憶體中。

合成器執行緒可以將優先順序給予不同的轉譯執行緒,以便先轉譯檢視區 (或附近) 中的項目。圖層也有多個不同解析度的平鋪,用於處理放大動作等事項。
將圖塊轉為點陣圖後,合成器執行緒會收集稱為「繪製四邊形」的圖塊資訊,以建立合成器影格。
繪製四邊形 | 包含資訊,例如圖塊在記憶體中的所在位置,以及考量網頁合成作業後,在網頁中繪製圖塊的位置。 |
合成器影格 | 代表網頁影格的繪圖四邊形集合。 |
接著,系統會透過 IPC 將合成器影格提交至瀏覽器程序。此時,您可以從 UI 執行緒新增另一個合成器影格,以便瀏覽器 UI 變更,或從其他轉譯器程序新增片段。這些合成器影格會傳送至 GPU,以便在螢幕上顯示。如果捲動事件傳入,合成器執行緒會建立另一個要傳送至 GPU 的合成器影格。

合成作業的好處是,它不會涉及主執行緒。合成器執行緒不需要等待樣式計算或 JavaScript 執行作業。因此,只合成動畫可提供最佳的流暢效能。如果需要再次計算版面配置或繪製作業,則必須使用主執行緒。
總結
在本篇文章中,我們探討了從剖析到合成的轉譯管道。希望您現在能進一步瞭解如何改善網站效能。
在本系列的下一篇也是最後一篇文章中,我們將進一步探討合成器執行緒,並瞭解 mouse move
和 click
等使用者輸入內容傳入時會發生什麼事。
你喜歡這篇文章嗎?如有任何問題或建議,歡迎在下方的留言區留言,或在 Twitter 上傳送訊息給 @kosamari。