瀏覽器渲染網頁的過程

湛風發表於2019-04-09

瀏覽器的主要功能是將使用者選擇的 web 資源呈現出來,它需要從伺服器請求資源,並將其顯示在瀏覽器視窗中,資源的格式通常是 HTML,也包括 PDF、image 及其他格式。

瀏覽器的執行緒

瀏覽器是多執行緒的,它們在核心制控下相互配合以保持同步。一個瀏覽器至少實現三個常駐執行緒:JavaScript 引擎執行緒,GUI 渲染執行緒,瀏覽器事件觸發執行緒。

  • GUI 渲染執行緒:負責渲染瀏覽器介面 HTML 元素,當介面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該執行緒就會執行。在 Javascript 引擎執行指令碼期間,GUI 渲染執行緒都是處於掛起狀態的,也就是說被”凍結”了。

  • JavaScript 引擎執行緒:主要負責處理 Javascript 指令碼程式

  • 定時器觸發執行緒:瀏覽器定時計數器並不是由 JavaScript 引擎計數的, JavaScript 引擎是單執行緒的, 如果處於阻塞執行緒狀態就會影響記計時的準確, 因此瀏覽器通過單獨執行緒來計時並觸發定時。

  • 事件觸發執行緒:當一個事件被觸發時該執行緒會把事件新增到待處理佇列的隊尾,等待 JS 引擎的處理。這些事件包括當前執行的程式碼塊如定時任務、瀏覽器核心的其他執行緒如滑鼠點選、AJAX 非同步請求等。由於 JS 的單執行緒關係所有這些事件都得排隊等待 JS 引擎處理。定時塊任何和 ajax 請求等這些非同步任務,事件觸發執行緒只是在到達定時時間或者是 ajax 請求成功後,把回撥函式放到事件佇列當中。

  • 非同步 HTTP 請求執行緒:在 XMLHttpRequest 在連線後是通過瀏覽器新開一個執行緒請求, 將檢測到狀態變更時,如果設定有回撥函式,非同步執行緒就產生狀態變更事件放到 JavaScript 引擎的處理佇列中等待處理。在發起了一個非同步請求時,http 請求執行緒則負責去請求伺服器,有了響應以後,事件觸發執行緒再把回到函式放到事件佇列當中。

瀏覽器渲染流程

1.解析 html,構建 dom 樹;解析 CSS 會產生 CSS 規則樹

瀏覽器解析 HTML 文件的原始碼,然後構造出一個 DOM 樹,DOM 樹的構建過程是一個深度遍歷的過程,當前節點的所有子節點都構建好以後才會去構建當前節點的下一個兄弟節點。

瀏覽器對 CSS 檔案內容進行解析,一般來說,瀏覽器會先查詢內聯樣式,然後是 CSS 檔案中定義的樣式,最後再是瀏覽器預設的樣式,構建 CSS Rule Tree。

2.構建(construct) render 樹

根據 DOM 樹和 CSSOM 樹來構造 Rendering Tree。注意:Rendering Tree 渲染樹並不等同於 DOM 樹,因為一些像 Header 或 display:none 的東西就沒必要放在渲染樹中了。

3.佈局(layout) render 樹

有了 Render Tree,瀏覽器已經能知道網頁中有哪些節點、各個節點的 CSS 定義以及他們的從屬關係,從而去計算出每個節點在螢幕中的位置。

4.繪製(painting) render 樹

按照算出來的規則,通過顯示卡,把內容畫到螢幕上。

5.迴流(reflow)

當瀏覽器發現某個部分發生了點變化影響了佈局,需要倒回去重新渲染,內行稱這個回退的過程叫 reflow。reflow 會從 這個 root frame 開始遞迴往下,依次計算所有的結點幾何尺寸和位置。

6.重繪(repaint)

改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部佈局的屬性時,螢幕的一部分要重畫,但是元素的幾何尺寸沒有變。

瀏覽器對 CSS 和 JS 的解析規則

CSS:

  • CSS 放在 head 中會阻塞頁面的渲染(頁面的渲染會等到 css 載入完成)
  • CSS 阻塞 JS 的執行 (因為 GUI 執行緒和 JS 執行緒是互斥的,因為有可能 JS 會操作 CSS)
  • CSS 不阻塞外部指令碼的載入(不阻塞 JS 的載入,但阻塞 JS 的執行,因為瀏覽器都會有預先掃描器)

JS:

  • 直接引入的 JS 會阻塞頁面的渲染(GUI 執行緒和 JS 執行緒互斥)
  • 非同步載入的 JS 不阻塞頁面的渲染(script 標籤中新增 async 或 defer 屬性)
  • JS 不阻塞資源的載入
  • JS 順序執行,阻塞後續 JS 邏輯的執行

HTML 頁面載入和解析流程

  1. 使用者輸入網址(假設是個 html 頁面,並且是第一次訪問),瀏覽器向伺服器發出請求,伺服器返回 html 檔案;
  2. 瀏覽器開始載入 html 程式碼,發現< head >標籤內有一個< link >標籤引用外部 CSS 檔案;
  3. 瀏覽器發出 CSS 檔案的請求,伺服器返回這個 CSS 檔案;(同時 GUI 渲染執行緒繼續執行,互不影響)
  4. 瀏覽器繼續載入 html 中< body >部分的程式碼,並且 CSS 檔案已經拿到手了,開始渲染頁面;
  5. 瀏覽器在程式碼中發現一個< img >標籤引用了一張圖片,向伺服器發出請求。此時瀏覽器不會等到圖片下載完,而是繼續渲染後面的程式碼;
  6. 伺服器返回圖片檔案,由於圖片佔用了一定面積,影響了後面段落的排布,因此瀏覽器需要回過頭來重新渲染這部分程式碼;
  7. 瀏覽器發現了一個包含一行 Javascript 程式碼的< script >標籤,直接執行;
  8. Javascript 指令碼操作某個元素,它命令瀏覽器隱藏掉程式碼中的某個標籤 (style.display=”none”)。瀏覽器會重新渲染這部分程式碼;
  9. 遇到 </html >,流程結束;
  10. 當使用者操作,頁面產生互動,瀏覽器發現某個部分發生了點變化影響了佈局,瀏覽器迴流(reflow);改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部佈局的屬性時,瀏覽器重繪(repaint)。

HTML 頁面載入優化

  • 頁面減肥。頁面的肥瘦是影響載入速度最重要的因素刪除不必要的空格、註釋。將 inline 的 script 和 css 移到外部檔案,可以使用 HTML Tidy 來給 HTML 減肥,還可以使用一些壓縮工具來給 JavaScript 減肥;

  • 減少檔案數量。減少頁面上引用的檔案數量可以減少 HTTP 連線數。許多 JavaScript、CSS 檔案可以合併最好合並;

  • 減少域名查詢。DNS 查詢和解析域名也是消耗時間的,所以要減少對外部 JavaScript、CSS、圖片等資源的引用,不同域名的使用越少越好;

  • 快取重用資料;

  • 優化頁面元素載入順序。首先載入頁面最初顯示的內容和與之相關的 JavaScript 和 CSS,然後載入 DHTML 相關的東西,像什麼不是最初顯示相關的圖片、flash、視訊等很肥的資源就最後載入;

  • 減少 inline JavaScript 的數量。瀏覽器 parser 會假設 inline JavaScript 會改變頁面結構,所以使用 inline JavaScript 開銷較大,不要使用 document.write()這種輸出內容的方法,使用現代 W3C DOM 方法來為現代瀏覽器處理頁面內容;

  • 使用現代 CSS 和合法的標籤。使用現代 CSS 來減少標籤和影象,例如使用現代 CSS+文字完全可以替代一些只有文字的圖片,使用合法的標籤避免瀏覽器解析 HTML 時做“error correction”等操作,還可以被 HTML Tidy 來給 HTML 減肥;

  • 不要使用巢狀 tables;

  • 指定影象和 tables 的大小。如果瀏覽器可以立即決定影象或 tables 的大小,那麼它就可以馬上顯示頁面而不要重新做一些佈局安排的工作,這不僅加快了頁面的顯示,也預防了頁面完成載入後佈局的一些不當的改變。

參考文章

  1. 網頁在瀏覽器上的渲染過程
  2. 效能優化——CSS和JS的載入和執行
  3. 瀏覽器渲染原理及流程

相關文章