瀏覽器頁面渲染機制

林暉發表於2019-03-01

分享目的: 解釋瀏覽器如何將 HTML、CSS 和 JavaScript 轉換為我們可以與之互動的網站,瞭解這個過程,可以幫助我們優化 Web 應用程式,從而獲得更快的速度和更好的效能。

問題: 瀏覽器如何渲染網站? (接下來會解構這個過程,但是首先,有必要了解一些基礎概念)

Web 瀏覽器是一種軟體,它從遠端伺服器(或者本地磁碟)載入檔案並將其顯示——使使用者可以與之互動。瀏覽器中有一個軟體叫瀏覽器引擎。在不同的瀏覽器中,瀏覽器的某個部分會根據它接收到的檔案確定顯示什麼,這就是所謂的瀏覽器引擎。瀏覽器引擎是每一種主流瀏覽器的核心軟體元件,不同的瀏覽器開發商用不同的名字來稱呼他們的引擎。

1. html解析

  • 接收資訊
    資料是以“資料包”的形式通過網際網路傳送,而資料包以位元組為單位。當你編寫一些 HTML、CSS 和 JS,並試圖在瀏覽器中開啟 HTML 檔案時,瀏覽器會從你的硬碟(或網路)中讀取 HTML 的原始位元組。
  • 計算機接收到位元組資料
    瀏覽器讀取的是原始資料位元組,而不是你編寫的程式碼的實際字元。瀏覽器讀取的是原始資料位元組,而不是你編寫的程式碼的實際字元。
  • 從 HTML 的原始位元組到 DOM,瀏覽器物件需要處理的是文件物件模型(DOM)物件。那麼,DOM 物件是從何而來的呢?首先,將原始資料位元組轉換為字元。(Bytes => haracters)
  • 從位元組到字元
    這一點,你可以通過你所編寫的程式碼的字元看到。這種轉換是基於 HTML 檔案的字元編碼完成的。至此,瀏覽器已經從原始資料位元組轉換為檔案中的實際字元。但這不是最終的結果。這些字元會被進一步解析為一些稱為“標記(token)”的東西。(Bytes => haracters => Tokens···)
  • 從字元到標記
    那麼,這些標記是什麼?文字檔案中的一堆字元對瀏覽器引擎而言沒什麼用處。如果沒有這個標記化過程,那麼這一堆堆字元只會生成一系列毫無意義的文字,即 HTML 程式碼——不會生成一個真正的網站。 當你儲存一個副檔名為.html 的檔案時,就向瀏覽器引擎發出了把檔案解析為 HTML 文件的訊號。瀏覽器“解釋”這個檔案的方式是首先解析它。在解析過程中,特別是在標記化過程中,瀏覽器會解析 HTML 檔案中的每個開始和結束“標籤(tag)”。解析器可以識別尖括號中的每個字串,如“< html>”、“< p>”

但標記還不是最終的結果。標記化完成後,接下來,標記將被轉換為節點。你可以將節點看作是具有特定屬性的不同物件。實際上,更好的解釋是,將節點看作是文件物件樹中的獨立實體。但節點仍然不是最終結果。 現在,讓我們看一下最後一點。在建立好之後,這些節點將被連結到稱為DOM 的樹資料結構中。DOM 建立起了父子關係、相鄰兄弟關係等。在這個 DOM 物件中,每個節點之間都建立了關係。現在,這是我們可以使用的東西了。

  • 標記
    但標記還不是最終的結果。標記化完成後,接下來,標記將被轉換為節點。你可以將節點看作是具有特定屬性的不同物件。實際上,更好的解釋是,將節點看作是文件物件樹中的獨立實體。但節點仍然不是最終結果。
  • DOM (Bytes => haracters => Tokens => Node => DOM) 讓我們看一下最後一點。在建立好之後,這些節點將被連結到稱為 DOM 的樹資料結構中。DOM 建立起了父子關係、相鄰兄弟關係等。在這個 DOM 物件中,每個節點之間都建立了關係。這個時候,是瀏覽器需要的東西了。

2.css解析

這個是我們很常見的寫法

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css"  href="test.css" />
</head>
<body>

</body>
</html>
複製程式碼

當瀏覽器接收到原始資料位元組並啟動 DOM 構建過程時,它還會發出請求來獲取連結的 test.css 樣式表。當瀏覽器開始解析 HTML 時,在找到 css 檔案的連結標籤的同時,它會發出請求來獲取它。可能你已經猜到,瀏覽器還是接收 CSS 資料的原始位元組,從網際網路或是本地磁碟。

瀏覽器如何處理這些 CSS 資料的原始位元組?

當瀏覽器接收到 CSS 的原始位元組時,會啟動一個和處理 HTML 原始位元組類似的過程。就是說,原始資料位元組被轉換成字元,然後標記,然後形成節點,最後形成樹結構。 什麼是樹結構?大多數人都知道 DOM 這個詞。同樣,也有一種 CSS 樹結構,,瀏覽器不能使用 HTML 或 CSS 的原始位元組。必須將其轉換成它能識別的形式,也就是這些樹形結構。

DOM + CSSOM = 渲染樹

渲染樹包含頁面上所有關於可見 DOM 內容的資訊以及不同節點所需的所有 CSSOM 資訊。注意,如果一個元素被 CSS 隱藏,例如使用 display; none,那麼節點就不會包含在渲染樹中。隱藏元素會出現在 DOM 中,但不會出現在渲染樹中。這是因為渲染樹結合了來自 DOM 和 CSSOM 的資訊,所以它知道不能把隱藏元素包含在樹中。

元素展示

我們已經得到了在螢幕上顯示元素所需的所有資訊。我們只要把它展示給使用者。這就是這個階段的全部工作。有了元素內容(DOM)、樣式(CSSOM)和計算得出的元素的精確佈局資訊,瀏覽器現在就可以將節點逐個“繪製”到螢幕上了。元素可以呈現在螢幕上了!


渲染阻塞資源

通俗的解釋為有東西阻止了螢幕上節點的實際繪製,在成功繪製之前,必須構造 DOM 和 CSSOM,因此,HTML 和 CSS 都是渲染阻塞資源。

  • JavaScript 如何執行?
    一個常用的 Web 應用程式肯定會使用一些 JavaScript。這是一定的。JavaScript 的“問題”在於你可以使用 JavaScript 修改頁面的內容和樣式。通過這種方式,你可以從 DOM 樹中刪除元素和新增元素,還可以通過 JavaScript 修改元素的 CSSOM 屬性。這很方便,但是同時也帶來了弊端
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>testRander</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <p>瀏覽器頁面渲染機制</p>
  <img src="http://spage.haimati.cn/activityImage/newplan.jpg">
</body>
</html>
複製程式碼

這是一個非常常見的文件。樣式表 style.css簡單定義樣式:

* {
  font-size: 20px;
}
body {
  background-color: antiquewhite;
}
複製程式碼

瀏覽器頁面渲染機制

一段簡單的文字和影象呈現在螢幕上。

根據前面的解釋,瀏覽器從磁碟(或網路)讀取 HTML 檔案的原始位元組並將其轉換為字元。字元被進一步解析為標記。當解析器遇到< link rel="stylesheet" href="style.css">時,就會請求獲取 CSS 檔案 style.css。DOM 構造繼續進行,當 CSS 檔案返回一些內容後,CSSOM 構造就開始了。

  • 引入 JavaScript
    每當瀏覽器遇到指令碼標籤時,DOM 構造就會暫停!整個 DOM 構建過程都將停止,直到指令碼執行完成。JavaScript 可以同時修改 DOM 和 CSSOM。由於瀏覽器不確定特定的 JavaScript 會做什麼,所以它採取的預防措施是停止整個 DOM 構造。
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>testRander</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <p id="title">瀏覽器頁面渲染機制</p>
  <img src="http://spage.haimati.cn/activityImage/newplan.jpg">
  <script>
      let title = document.getElementById("title");
      console.log("title is: ", title);
  </script>
</body>
</html>
複製程式碼

瀏覽器頁面渲染機制
當把js放到元素之前的話

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>testRander</title>
 <link rel="stylesheet" href="style.css">
</head>
<body>
   <script>
       let title = document.getElementById("title");
       console.log("title is: ", title);
   </script>
 <p id="title">瀏覽器頁面渲染機制</p>
 <img src="http://spage.haimati.cn/activityImage/newplan.jpg">
</body>
</html>
複製程式碼

瀏覽器頁面渲染機制

當指令碼試圖訪問一個 id 為 header 的 DOM 節點時,由於 DOM 還沒有完成對文件的解析,所以它還不存在。這把我們帶到了另一個重要的問題。指令碼的位置很重要。

在預設情況下,每個指令碼都是一個解析器阻斷器!DOM 的構建總是會被打斷。不過,有一種方法可以改變這種預設行為。如果將 async 關鍵字新增到指令碼標籤中,那麼 DOM 構造就不會停止。DOM 構造將繼續,指令碼將在下載完成並準備就緒後執行。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>testRander</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <script src="test.js" async></script>
  <p id="title">瀏覽器頁面渲染機制</p>
  <img src="http://spage.haimati.cn/activityImage/newplan.jpg">
</body>
</html>
複製程式碼

把js放入test.js中進行引入

let title = document.getElementById("title");
console.log("title is: ", title);
複製程式碼

瀏覽器頁面渲染機制

這樣DOM的構建就不會停止,指令碼在構造完成後執行。

相關文章