【瀏覽器】瀏覽器基本工作原理

木子草明發表於2021-04-30

1.瀏覽器內部組成

我們先來看瀏覽器的內部組成(以chrome為例):

我們看到瀏覽器主要包括:

  • 1個瀏覽器主程式
    主要負責介面顯示,使用者互動,子程式管理

  • 多個渲染程式
    一般瀏覽器會為每個Tab標籤視窗建立一個渲染程式,主要負責將html,css,JavaScript轉換成我們看到的網頁,裡面包含多個執行緒,比如JavaScript的V8引擎。

  • 1個GPU程式
    主要負責複雜的計算,比如3D動畫,圖形繪製。

  • 1個網路程式
    主要負責網路資源載入

  • 多個外掛程式
    瀏覽器器每個外掛都會分配一個外掛程式。

2.從一個url開始

我們下面來看在位址列輸入一個url後,瀏覽器做了什麼事,我們先來看下流程圖:
在這裡插入圖片描述
下面我們來分析下上面的流程圖:

  • 當使用者在位址列輸入一個地址或者關鍵字,並按下Enter鍵的時候,意味著當前頁面很快要被替換,在這個時候會觸發當前頁面的beforeunload事件。
    然後瀏覽器的當前tab欄就變成載入狀態,變成一個轉動的圓圈,此時頁面還沒有開始改變,需要等到後面“提交文件”後,才會別新內容替換。
  • 瀏覽器主程式合成完整Url:如果是輸入的是地址,比如 "baidu.com",則自動合成為:https://www.baidu.com/。
    如果輸入的是關鍵字,則使用預設搜尋引擎,合成帶搜尋關鍵字Url,比如輸入:'hello',預設搜尋引擎為百度,則合成為:https://www.baidu.com/s?ie=UTF-8&wd=hello
    然後把完整url傳送給網路程式。
  • 網路程式接收到url請求後,先判斷是否本地快取了資源。如果有,則直接返回資源給瀏覽器主程式,不發起網路請求。如果沒有快取,則進入網路請求。
  • 網路請求之前,先要進行DNS解析,把域名轉換成ip,這一步也是先查DNS快取,如果有當前域名的快取,則從快取中直接取對應ip。
    如果沒有快取,則從DMS伺服器請求ip。然後構建請求體,請求頭(包括cookie)等資訊,向服務端傳送網路請求(建立Tcp連結)。
  • 服務端接收到請求訊息後,進行對應操作,然後生成響應資料,傳送給網路程式。
  • 網路程式接收到伺服器返回的響應資料後,先解析響應頭資訊,判斷狀態碼是否為重定向(3xx),如果是,則取響應頭中Location欄位,重新發起請求。
    如狀態碼為200,表示請求成功,可以繼續處理請求。
  • 如果狀態碼為200,瀏覽器主程式會根據響應頭中的Content-Type欄位做出響應對策,如果此欄位的值為application/octet-stream,則啟動下載流程。
    如果Content-Type為text/html,則啟動渲染流程。
  • 預設情況下,瀏覽器會為每一個tab頁籤建立一個渲染程式,但是如果是同一個站點(根域名+協議相同,埠+子域名不同),則共用一個渲染程式。
  • 進入渲染流程開始前,瀏覽器主程式會傳送一個“接收文件”訊息給渲染程式,這裡的文件是指存在網路程式裡面的響應體資訊。
  • 渲染程式接收到“提交文件”的訊息後,會和網路程式建立一個通道,接收資料。
  • 渲染程式接收到資料後,開始向瀏覽器主程式傳送“確認提交”,訊息
  • 瀏覽器主程式接收到“確認提交”的訊息後,開始更新瀏覽器頁面,包括:位址列的url,前進後退按鈕。
  • 渲染程式開始生成頁面,這個過程是一邊接收一邊生成。當頁面渲染完畢後(當前頁面及內部iframe都出發了onload事件),傳送“渲染完畢”訊息。
  • 瀏覽器主程式接收到訊息後,顯示頁面,並停止標籤欄的載入動畫。

到這裡為止,當我們在位址列輸入一個url,然後到頁面展示在我們面前的大致流程就梳理完畢了。但是這裡面還有一個非常重要的環節,就是頁面解析的流程我們上面只是一帶而過,這是渲染程式來做的工作,下面來具體展開。

3.渲染程式

渲染程式的核心工作就是解析接收到的html/js/css程式碼,並將其轉換成使用者可互動的頁面。
渲染進成包含:

  • 主執行緒GUI:負責解析dom結構
  • js引擎執行緒:負責執行js程式碼,會阻塞主程式。
  • 合成執行緒:分組,合成,並把視口附近圖塊提交給光柵化執行緒。
  • 多個光柵化執行緒:生成點陣圖,即頁面需要的每個畫素點的顏色值(我們看到的頁面其實就是每個畫素點的顏色)
    在這裡插入圖片描述
    下面來分析以下流程圖:
  • 渲染程式開始接受到資料的時候,為了提高效率,會先預掃描接收到的資料,如果如果發現有需要載入資源的標籤(img,link,外部script等),就先告訴瀏覽器主程式,先去下載,這個過程叫預解析,這個任務交出去後,就繼續做自己本職工作,解析html檔案。
    -當主執行緒解析html檔案時,會碰到三種型別資料:html標籤,css程式碼,js程式碼。
    • html標籤:對於普通的html標籤,會生成Dom樹(標籤節點的結構樹,是瀏覽器的內建物件,會有一些內建方法和屬性)。
    • css樣式:對於css程式碼,會根據css的樣式選擇器構建cssDom樹,並對樣式進行計算(rem,em轉換為px,沒有定義樣式的提供預設樣式),生成computedStyle
      如果遇到的是css外部連結,如果從預解析開始還沒下載完,則繼續下載,不會阻塞解析。
    • js程式碼:對於js程式碼,會先判斷js程式碼前的css有沒有解析完(包括外部css的下載),如果沒有則等待css程式碼下載完並解析完畢,然後再執行js程式碼。js執行期間阻塞解析。所以步驟是這樣:
      遇到js -> 阻塞dom樹構建 -> css下載 -> css解析->js執行->繼續構建dom樹
    • js連結:對於js的連結,如果標籤上沒有設定非同步標誌(async/defer),則和普通的js程式碼一樣,下載也會阻塞dom解析,也需要等css下載解析完,但是css下載不會阻塞js下載,步驟如下:
      遇到js連結(無非同步標籤) -> 阻塞dom樹構建 -> css下載(同時js下載) -> css解析->js執行->繼續構建dom樹
      如果有非同步標籤,則下載不阻塞dom樹構建,async檔案下載完,立即執行。defer檔案下載完,等html解析完,按載入順序執行。步驟如下:
      遇到js連結(async) ->下載js(不影響dom構建) -> js下載完畢 -> 立即執行js(走普通js程式碼流程)
      遇到js連結(defer) ->下載js(不影響dom構建) -> js下載完畢 -> 等html解析完畢 -> 按順序執行js
  • 等dom樹和computedStyle都構建完畢後(要都構建完畢), 更具dom樹和computedStyle,構建佈局樹layoutTree,佈局樹包含每個節點的位置座標和盒模型的大小,並且剔除了隱藏的節點(樣式設定了display:none的節點)。
  • 等佈局樹layoutTree構建完畢後,我們已經知道了頁面上要顯示的每個節點的大小,位置和樣式。繼續來主執行緒會對節點進行分層,通過遍歷layoutTree構建圖層樹layerTree。哪些節點會被分為一層呢?分為兩種情況:
    • 擁有層疊上下文屬性的元素會被單獨提升為一層(什麼是層疊上下文),包含設定了z-index,transform,will-change,filter,opacity<1,flex子元素等等。
    • 需要裁剪的地方會被分為一層,即元素的大小被限制,而內容超出元素大小,內容被裁剪。
  • 圖層樹layerTree被建立後,會為每一個圖層建立繪製指令列表,可以再瀏覽器除錯視窗的layers標籤下檢視分層和指令列表資訊。渲染程式的主執行緒把繪製指令生成後,並不執行,而是轉交給合成執行緒。
  • 合成執行緒先把圖層分為圖塊(大小通常為256256/512512),然後把瀏覽器使用者視口附近的圖塊優先交給柵格化執行緒來生成點陣圖。
  • 柵格化的最小執行單位是圖塊,即最少要把一個圖塊柵格化。柵格化的過程通常會用GPU執行,就是說柵格化執行緒會把繪製圖塊的指令傳送給GPU,然後GPU生成圖塊的點陣圖(畫素點的顏色值),存在GPU記憶體。
  • 當視口附近所有圖塊柵格化完畢後,合成執行緒傳送DrawQuad指令給瀏覽器主程式,瀏覽器主程式把頁面的內容顯示在螢幕上。

4.應用

那麼知道了瀏覽器的基本原理後,對我們開發有什麼實際的作用呢?以下總結了幾點:

  • css會阻塞js,js會阻塞dom解析,所以儘量把css檔案放頁面上面,js放在頁面下面。
  • 對於不會影響頁面內容的js外部檔案,可以用async/defer標記來非同步載入。
  • css動畫效率比js操作dom實現動畫好,因為css動畫的只會引起合成及以後步驟的重新執行。而合成步驟是在合成執行緒,不會阻塞渲染的主執行緒。而js如果影響到dom節點的大小樣式位置,則需要觸發佈局及以後的步驟。

參考:

相關文章