身為與瀏覽器朝夕相處的前端工程師,除了會「使用」它之外,理解背後的架構與運作模式,能幫助我們在更底層的角度找到效能優化的可能性。本章先從瀏覽器架構的演進切入,再走一遍現代瀏覽器的渲染管線(Rendering Pipeline)。

舊石器時代:單一程序架構#

2007 年以前的瀏覽器幾乎都是 Single Process 的設計,所有功能模組都跑在同一個程序裡:JavaScript 執行環境、網路、外掛(Plugin)、渲染引擎等等。這種架構衍生出三個明顯問題:

  • 不穩定:任何一個模組崩潰都會把整個瀏覽器拖下水。
  • 不流暢:頁面、JS 與外掛共用一條 thread,遇到無限迴圈就會整個失去回應;記憶體也很難完全回收。
  • 不安全:缺乏沙盒隔離,外掛或 Script 有機會直接存取系統資源。
function stupidFunc() {
  while (true) {
    console.log("HI");
  }
}
stupidFunc();

在 Single Process 的模型下,這段程式會直接吃光整個 thread,讓所有分頁都動彈不得。

新石器時代:多程序架構#

2008 年 Chrome 提出 Multi Processes 架構,把渲染(Renderer Process)與外掛(Plugin Process)獨立成不同程序,再透過 IPC 通訊。它一次解決三個老問題:

  • 穩定性:某個分頁或外掛掛掉,只會影響自己所在的程序。
  • 流暢性:每個分頁獨立的 Renderer Process 關閉時,作業系統會回收整塊記憶體,自然解決長時間使用造成的 memory bloat。
  • 安全性:Plugin Process 與 Renderer Process 都跑在沙盒(Sandbox)中,惡意程式碼無法輕易突破隔離環境。

現代:再進一步拆分#

現在的 Chrome 在多程序基礎上又獨立出 Network Process 與 GPU Process,分別負責網路資源載入與繪製運算。獨立程序帶來穩定、安全與平行化的好處,但同時也付出代價:

  • 整體記憶體佔用更高。
  • 程序之間需要更多 IPC 通訊,架構複雜度上升。

未來:服務導向架構#

2016 年 Chrome 團隊提出以服務導向架構(SOA, Services Oriented Architecture)為基礎的新模型,將瀏覽器內的功能拆成可組合的「服務」,並依硬體狀況彈性整併:

  • 在效能充裕的裝置上,每個服務獨立成一個程序,得到最佳的隔離與平行化。
  • 在資源受限的裝置上,會把多個服務合併到單一程序,省下記憶體開銷。

補充說明 SOA 的概念與微服務(Microservices)相近,但兩者在邊界、部署與治理層面仍有差異,這也是為什麼 Chrome 採用 SOA 的角度去重組瀏覽器內部模組。

渲染引擎的運作機制#

每一個分頁都有獨立的 Renderer Process。Chrome 自 2018 年起更進一步推動 Site Isolation:跨來源的 iframe 也會運行在獨立的 Renderer Process,徹底落實同源政策的安全模型。不過符合 Same Site 條件、且由原頁面開啟的新分頁,預設會與原頁面共用同一個 Renderer Process,以節省資源並支援共享的 JavaScript 環境。

當瀏覽器收到 HTML 後,渲染引擎會走過大致以下流程:

  • 解析 HTML 產生 DOM Tree。
  • 解析 CSS 產生 CSSOM Tree。
  • DOM 與 CSSOM 結合形成 Render Tree。
  • 進行版面配置(Layout)計算每個元素的位置與大小。
  • 對每個節點產生繪製(Paint)指令。
  • Compositor Thread 將節點切分成合成層(Compositing Layer)並組合成畫面。

分層與柵格化#

為了支援滾動、3D 等複雜效果,瀏覽器會在 Layout 之後再產生 Layer Tree。Renderer Process 的 Main Thread 在此階段做的並不是真的繪圖,而是「產生繪製指令」。指令完成後,會交給 Compositor Thread,再切成更小的 tile,由 raster threads 負責柵格化(rasterize),並把結果存到 GPU 記憶體中。最後 Compositor Thread 整合 draw quads 產生 Compositor Frame,透過 IPC 送到 Browser Process,再丟給 GPU 顯示。

Reflow、Repaint 與 Compositing#

頁面更新時,會觸發以下三種行為的其中之一:

  • Reflow(回流):改變幾何屬性,如 width、height、top,整個渲染流程都得重跑。
  • Repaint(重繪):只改變顏色、背景等樣式,跳過版面配置,從繪製重新開始。
  • Composition(合成):只動到合成層的屬性(例如 transform),完全跳過 Reflow 與 Repaint,且合成的工作是在 Compositor Thread 與 Raster Thread 上進行,不佔用 Main Thread。

實務建議 CSS 動畫之所以推薦使用 transform,正是因為它能落在 Composition Only 的路徑上,避免昂貴的版面配置與重繪計算。

<style>
  .box {
    transform: translate(10px, 20px);
    will-change: transform;
  }
</style>

避免多餘的 Reflow 與 Repaint#

  • 不要連續用多個 statement 修改 style,建議改用切換 class 的方式一次帶過。
  • 把所有讀取(例如 offsetHeight)集中處理完,再一起寫入,可避免反覆觸發 Reflow。
  • 對於需要動畫的元素,優先選用 transformopacity

理解瀏覽器架構與渲染管線並不會立刻讓網站變快,但它提供了「為什麼這樣寫比較快」的判斷依據。當效能問題出現時,這份底層知識能讓我們直接定位到瓶頸所在。

原文出處#