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

1.瀏覽器內部組成

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

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

  • 1個瀏覽器主進程
    主要負責界面顯示,用戶交互,子進程管理

  • 多個渲染進程
    一般瀏覽器會為每個Tab標籤窗口創建一個渲染進程,主要負責將html,css,JavaScript轉換成我們看到的網頁,裏面包含多個線程,比如JavaScript的V8引擎。

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

  • 1個網絡進程
    主要負責網絡資源加載

  • 多個插件進程
    瀏覽器器每個插件都會分配一個插件進程。

2.從一個url開始

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

  • 當用戶在地址欄輸入一個地址或者關鍵字,並按下回車鍵的時候,意味着當前頁面很快要被替換,在這個時候會觸發當前頁面的beforeunload事件。
    然後瀏覽器的當前tab欄就變成加載狀態,變成一個轉動的圓圈,此時頁面還沒有開始改變,需要等到後面「提交文檔」後,才會別新內容替換。
  • 瀏覽器主進程合成完整Url:如果是輸入的是地址,比如 “baidu.com”,則自動合成為://www.baidu.com/。
    如果輸入的是關鍵字,則使用默認搜索引擎,合成帶搜索關鍵字Url,比如輸入:’hello’,默認搜索引擎為百度,則合成為://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節點的大小樣式位置,則需要觸發布局及以後的步驟。

參考:

Tags: