應該有很多前端開發人員都思考過這麼一個問題:從輸入 URL 到頁面載入完成,中間都做發生了什麼?
這個問題涉及的面非常廣,每個涉及的點又很深入。從觸屏/鍵盤如何到 CPU?CPU 如何到系統核心?如何從作業系統 GUI 到瀏覽器?瀏覽器如何向網路卡傳送資料?資料如何從本機網路卡傳送到伺服器?伺服器接收資料後如何處理?伺服器返回資料後瀏覽器如何處理?瀏覽器如何將頁面展現出來?等等等等,每一個過程都包含了大量且深入的知識體系,很難一以貫通。
但作為前端開發人員,瀏覽器是我們的主要工具之一,瀏覽器是如何將頁面展現出來的則是我們更關注的部分。因此本文就從一些基本流程來簡要描述這個過程。
從上面這個圖中可以發現,雖然使用的 Javascript 是單執行緒語言,但瀏覽器本身是多程式的。
但是這並不是從一而終的狀態,而是瀏覽器從早期的單程式結構逐漸發展發展而來。現代瀏覽器各程式根據負責的功能不同,分為瀏覽器程式、渲染器程式、網路程式、GPU 程式、快取程式、外掛程式等等。為了更好的理解瀏覽器頁面的呈現過程,我們以最主流的 Chrome 為例,簡要的說明一下各個程式的大致職能:
-
瀏覽器程式: 負責控制介面展示、使用者互動、子程式管理等功能。
-
渲染器程式: 負責將 HTML\CSS\JS 轉化為使用者可以與之互動的網頁。渲染引擎如 webkit、blink 和 JS 引擎 V8 都是在該程式之中。
-
GPU 程式: GPU 程式原本是為了實現 3D CSS 效果,但是隨後頁面、Chrome 的 UI 都採用 GPU 來繪製,是 GPU 成為了重要需求,於是增加了 GPU 程式。
-
網路程式: 負責頁面的網路資源載入。
-
外掛程式:負責外掛的執行,由於外掛可能崩潰,需要外掛程式其他程式隔離。注意,外掛並不是我們常用的瀏覽器擴充,plugin 和 extension 是不同的。
-
快取程式:負責處理頁面資源快取和清理。
我們本次需要重點關注的是渲染器程式。
回到問題,當我們在瀏覽器位址列輸入地址時,瀏覽器程式的 UI 執行緒會捕捉輸入內容,如果訪問的是網址,那麼 UI 執行緒會啟動一個網路執行緒來構建請求(這裡我們暫時不考慮快取,快取又是另外一個故事了),它請求 DNS 進行域名解析然後連線伺服器獲取資料。如果我們輸入的是關鍵詞,瀏覽器則使用預設配置的搜尋引擎來搜尋。在獲取到資料並通過安全校驗後,網路執行緒會通知 UI 執行緒資料準備完畢,然後UI執行緒建立一個渲染器程式來進行頁面的渲染,並將資料通過 IPC 管道傳遞給渲染器程式。
至此,我們的主角渲染器程式登場!
解析 HTML
渲染器程式接收到的是一個 HTML,需要把 HTML 解析成 DOM 資料結構。因為直接的 HTML 位元組流是無法被渲染引擎所理解的,必須轉化成可以理解的內部結構。這個內部結構就是 DOM,DOM 提供了對 HTML 文件的結構化表述。在渲染引擎中,DOM 有三個層面的作用:
-
從頁面角度:DOM 是生成頁面的基礎資料結構。
-
從 js 角度:DOM 提供了 js 操作的介面。通過這套介面,js 可以對 DOM 介面進行訪問,從而使開發者擁有改變文件結構、樣式、內容的能力。
-
從安全形度:DOM 是 HTML 經過解析的內部資料結構,它將 web 頁面和 js 連結起來,並過濾了一些不安全的內容。
渲染器程式內部使用 HTML Parser 將 HTML 解析成 DOM 結構。需要注意的是,HTML 解析器不會等待整個 HTML 文件載入完畢再去解析,而是載入多少了多少 HTML,就解析多少。
那麼 HTML 位元組流是如何轉換成 DOM 的呢?
其實和 V8 解析 js 類似,也是做詞法分析,通過分詞器將位元組流成功成一個個 token,包括 Tag token 和文字 token。HTML 解析器維護了一個 token 棧結構,token 會按照對應順序入棧出棧,然後將 token 解析成 DOM 節點,並將 DOM 節點新增進 DOM 樹中。
前面提到生成 DOM 可以過濾一些不安全內容。這主要是渲染引擎中的一個名為XSSAuditor 安全檢查模組實現的。它會監測詞法安全,在分詞器解析出 token 之後,檢查這些模組是否引用了外部指令碼,是否符合 CSP 規範,是否存在跨站點請求等。如果出現不符合規範的內容。XSSAuditor 會對該指令碼或下載任務進行攔截。
DOM 樹在構建過程中會建立 document 物件,然後以 document 為根節點的 DOM 樹不斷修改向其中新增新的元素。
解析 CSS
前面已經將 HTML 解析成 DOM 樹了,但是光擁有 DOM 樹還不足以讓我們知道頁面的樣貌。因為我們肯定會為頁面設定一些樣式。因此主程式還會解析頁面中的 CSS 從而確定每個 DOM 節點的計算樣式(computed style)。
CSS 的樣式來源主要有三個:
-
通過 link 引用的外部 CSS 檔案
-
使用