深度理解瀏覽器前端優化

優化關乎速度和滿意度。
  • 從用戶體驗(UX)角度,我們希望前端網頁可以快速加載
  • 從開發體驗(DX)角度,我們希望前端是快速,簡潔,規範的

瀏覽器都做了什麼?

我們希望瀏覽器打開一個簡單的網頁

<!DOCTYPE html>
<html>
  <head>
    <title>The "Click the button" page</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="styles.css" />
  </head>
  
  <body>
    <h1>
      Click the button.
    </h1>
    
    <button type="button">Click me</button>
    
    <script>
      var button = document.querySelector("button");
      button.style.fontWeight = "bold";
      button.addEventListener("click", function () {
        alert("Well done.");
      });
    </script>
  </body>
</html>

瀏覽器如何渲染網頁
  1. 使用 HTML 創建文檔對象模型(DOM
  2. 使用 CSS 創建 CSS 對象模型(CSSOM
  3. 基於 DOMCSSOM 執行腳本(Scripts
  4. 合併 DOMCSSOM 形成渲染樹(Render Tree
  5. 使用渲染樹佈局(Layout)所有元素
  6. 渲染(Paint)所有元素

附圖
在這裏插入圖片描述

步驟一 — HTML

瀏覽器從上到下讀取標籤,把他們分解成節點,從而創建 DOM 。

附圖

HTML 加載優化策略
  • 樣式在頂部,腳本在底部

總體思路是儘可能早的加載樣式,儘可能晚的加載腳本。原因是腳本執行之前,需要 HTML 和 CSS 解析完成,因此,樣式儘可能的往頂部放,當底部腳本開始執行之前,樣式有足夠的時間完成計算。

進一步講講如何優化

  • 最小化和壓縮

方法可用於所有內容,包括 HTML,CSS,JavaScript,圖片和其它資源。

最小化是移除所有多餘的字符,包括空格,註釋,多餘的分號,等等。

壓縮比如 GZip,大大壓縮下載文件的大小

兩種方法都用的情況下,資源加載量減少了 80% 到 90%。比如:bootstrap 節省了 87% 的流量。

  • 無障礙

不會提升頁面的下載速度,但會大大提升殘障人士的滿意度。給元素加上 aria 標籤,圖片提供 alt 文本,HTML 5 無障礙參見。

使用諸如 WAVE 的工具鑑別哪些地方可以提高可訪問性。

步驟二 — CSS

當瀏覽器發現任何與節點相關的樣式時,比如:外部,內部,或行內樣式,立即停止渲染 DOM ,並利用這些節點創建 CSSOM。這就是 CSS “渲染阻塞“ 的由來。這裏是不同類型樣式的優缺點。

//外部樣式
<link rel="stylesheet" href="styles.css">
// 內部樣式
<style>
  h1 {
    font-size: 18px;
  }
</style>
// 行內樣式
<button style="background-color: blue;">Click me</button>

CSSOM 節點創建與 DOM 節點創建類似,隨後,兩者合併如下:

附圖

CSSOM 的構建會阻塞頁面的渲染,因此我們想盡早加載樣式,

CSS 加載優化策略
  • 使用 media 屬性

media 屬性指定加載樣式的條件,比如:符合最大或最小分辨率?還是面向屏幕閱讀器?

  • 延遲加載 CSS

有些樣式,比如:首屏以下的,或者不那麼重要的,可以等待首屏最有價值的內容渲染完成再加載,可以使用腳本等待頁面加載,然後再插入樣式。

這有兩個栗子:The future of loading CSS,Defer load CSS

  • 只加載需要的樣式

使用 uncss 類似的工具,儘量移除不需要的樣式。

步驟三 — JavaScript

瀏覽器不斷構建 DOM / CSSOM 節點,直到發現外部或者行內的腳本。

由於腳本可能需要訪問或操作之前的 HTML 或樣式,我們必須等待它們構建完成。

因此瀏覽器必須停止解析節點,完成構建 CSSOM,執行腳本,然後再繼續。這就是 JavaScript 被稱作**“解析器阻塞”**的原因。

腳本只能等到先前的 CSS 節點構建完成。

附圖

JavaScript 加載優化策略
  • 異步加載腳本

腳本添加 async 屬性,可以通知瀏覽器不要阻塞其餘頁面的加載,下載腳本處於較低的優先級。一旦下載完成,就可以執行。

附圖

async 適用於不影響 DOM 或 CSSOM 的腳本,對一些跟我們的代碼無關的,不影響用戶體驗的外部腳本尤其適用,比如:分析統計腳本。

  • 延遲加載腳本

deferasync 非常相似,不會阻塞頁面加載,但會等到 HTML 完成解析後再執行。

附圖在這裏插入圖片描述

使用 defer 策略的 另一個好選擇,或者也可以使用 addEventListener,瞭解更多,參加這裏。

不幸的是 asyncdefer 對於行內的腳本不起作用,瀏覽器默認會編譯執行它們。

  • 操作之前克隆節點

多次操作 DOM 時可以嘗試,首先克隆整個 DOM 節點更加高效,操作克隆後的節點,然後替換先前的節點,避免了多次重繪,降低了 CPU 和內存消耗,同時也避免了不必要的頁面閃爍。

需要注意,克隆的時候並沒有克隆事件監聽。

  • Preload/Prefetch/Prerender/Preconnect

這些新屬性並不是所有的瀏覽器都支持。瞭解詳情可以看這裏:Prefetching, preloading, prebrowsing

步驟四 — 渲染樹(Render Tree)

一旦所有節點已被解析,DOM 和 CSSOM 準備合併,瀏覽器便會構建渲染樹。如果我們把節點想象成單詞,那麼對象模型就是句子,渲染樹便是整個頁面。

附圖

步驟五 — 佈局(Layout)

佈局階段需要確定頁面上所有元素的大小和位置。

附圖

步驟六 — 渲染(Paint)

最終的渲染階段,會真正地光柵化屏幕上的像素,把頁面呈現給用戶。

附圖

整個過程耗時1秒或十分之一秒,我們的任務是讓它更快。

如果 JavaScript 事件改變了頁面的某部分,便會引起渲染樹的重繪,並且迫使佈局(Layout)和渲染(Paint)過程再次進行。

瀏覽器如何發起網絡請求

當瀏覽器請求一個 URL,服務端會響應一些 HTML。

我們需要認識一個新術語,關鍵渲染路徑(Critical Rendering Path (CRP)),就是瀏覽器渲染頁面的步驟數,如下圖。

附圖

關鍵路徑長度

關鍵渲染路徑的度量標準是路徑長度。最理想的關鍵路徑長度是1。

如果頁面包含一些內部樣式和 JavaScript ,關鍵路徑發生以下改變。

附圖

新增兩步,構建 CSSOM和執行腳本,因爲我們的 HTML 有內部樣式和腳本需要計算。由於沒有外部請求,我們的關鍵路徑長度沒變。

但是注意,我們的 HTML 大小增加到了 2kb,某些地方還是受了影響。

關鍵字節數

三個度量標準之二出現了,關鍵字節數,它用來衡量渲染頁面需要傳送多少字節數。

如果你認爲不需要外部資源,就大錯特錯了,外部資源可以被緩存。

我們使用一個外部 CSS 文件,一個外部 JavaScript 文件,和一個外部帶 async 屬性的 JavaScript 文件。關鍵路徑圖如下:

附圖

瀏覽器請求頁面,構建 DOM,發現外部資源後開始下載,CSS 和 JavaScript 有較高的優先級,其它資源次之。

styles.cssapp.js 通過另一個關鍵路徑獲取。暫時不獲取 analytics.js ,因爲加了 async 屬性,瀏覽器將用另一個線程下載它,它處於較低優先級,不會阻塞頁面渲染,也不影響關鍵路徑。

關鍵文件

最後一個度量標準是關鍵文件,瀏覽器渲染頁面需要下載的文件總量。以上例子,HTML 文件,CSS 和 JavaScript 文件算關鍵文件,async 的腳本不算。當然是文件越少越好。

回到關鍵路徑長度

以上例子就是最長的渲染路徑嗎?我認爲渲染頁面時,我們僅需要下載 HTML,CSS 和 JavaScript 文件,僅通過兩次服務器往返就做到了。

HTTP1 文件限制

我們瀏覽器的 HTTP1 協議,在同一個域名,同一次,允許下載的文件數有最大限制,範圍從 2(老舊的瀏覽器)到 6(Edge,Chrome)。

各種瀏覽器請求文件的最大併發數,參見Maximum concurrent connections to the same domain for browsers。

通過把一些資源存放到影子域名,可以繞過這個限制,以達到最佳優化效果。

注意:不要把關鍵的 CSS 放到根域名之外的其他域名,有些場景下會對 DNS 查找和延遲起反作用。

HTTP2

如果網站使用了 HTTP2,並且用戶的瀏覽器也兼容,則可以完全避開這個下載限制。

這裏有個 HTTP2 測試網站。

TCP 往返限制

每一次服務器往返可以傳送的最大數據量是 14kb,包括所有 HTML,CSS 和腳本的網絡請求。

如果我們的 HTML,或者積累的資源請求超過 14kb時,需要多做一次服務器往返。

大魔法師

我們整個 HTML 頁面可以很好的壓縮, GZip 可以壓縮到 2kb,遠低於 14kb 的限制,因此,一次服務器往返就可以搞定。

附圖

關鍵路徑度量: 長度 1,文件數 1,字節數 2kb

瀏覽器發現外部資源(CSS 和 JavaScript)時,發起請求開始下載它們。首要下載的 CSS 文件是 14kb,達到了往返傳輸的最大限制,因此增加了一條關鍵路徑。

附圖

關鍵路徑度量: 長度 2,文件數 2,字節數 16kb

餘下的資源低於 14kb,但是總共有 7 個資源,由於網站未啓用 HTTP2,我們的 Chrome,每一次往返僅可以下載 6 個文件。

附圖

關鍵路徑度量: 長度 3,文件數 8,字節數 28kb

下載完最終文件,並開始渲染 DOM。

附圖

關鍵路徑度量: 長度 4,文件數 9,字節數 30kb

基於以上的信息和知識,發起每個連接時,就可以準確地預估頁面的性能了。

瀏覽器網絡優化策略
  • Pagespeed Insights

使用 Insights 鑑別性能問題,Chrome DevTools 也有個 audit 標籤。

  • 充分利用 Chrome 開發者工具

這篇文章 值得一讀,幫你理解網絡資源

  • 在優質的環境裏開發,在艱苦的環境裏測試

開發時大可使用 1Tb SSD,32G 內存的 Macbook Pro ,但是性能測試時還是要到 Chrome 的 network 標籤下模擬低帶寬的情形,從而獲取有價值的信息。

  • 合併資源/文件

基本上,每接收到一個外部 CSS 和 JavaScript 文件,瀏覽器都會構建 CSSOM,執行腳本。儘管幾個文件可以在一次往返中傳送,但也浪費了瀏覽器的寶貴時間和資源,最好還是合併文件,減少不必要的加載。

首屏內容使用內部樣式
內部 CSS 和 JavaScript 不需要請求外部資源,相反,外部資源又可以被緩存,並保持 DOM 輕量,兩者沒有非黑即白。

但是一個非常好的論點是首屏關鍵內容使用內部樣式,可以避免請求額外的資源,節省時間做最有意義的渲染。

  • 最小化/壓縮圖片

  • 延遲加載圖片

  • 異步記載字體

  • 是否真正需要 JavaScript / CSS?

原生 HTML 元素可以實現的行爲是否用了腳本?是否有樣式或者圖標可以行內創建的,不需要內部/外部資源?比如:行內 SVG。

  • CDN

可以利用 CDN(內容分發網絡)存儲資源,它會從離用戶最近,延遲最低的位置分發到用戶設備,加載時間更快。

延伸閱讀

綜述

關鍵渲染路徑是最重要的,它使得網站優化有規律可循。需要關注3個指標:

1—關鍵字節數
2—關鍵文件數
3—關鍵路徑長度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章