轉譯器程序的內部運作方式
這是部落格系列文章的第 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。