天天看點

前端性能優化(六)

分析關鍵呈現路徑性能

确定并解決關鍵呈現路徑性能方面的瓶頸需要了解很多常見問題。讓我們開始實踐之旅,并找出常用的性能模式,進而幫助您優化網頁。

優化關鍵呈現路徑的目标是允許浏覽器盡可能快地繪制網頁:較快的頁面呈現速度可以提高互動度、增加網頁浏覽量并提高轉化率。是以,我們希望通過優化要加載的資源和加載順序,盡可能減少通路者注視空白螢幕的時間。

為了更好地介紹這一過程,我們先從最簡單的情況開始講解,逐漸建構我們的網頁,使其包含更多資源、樣式和應用邏輯;在此過程中,我們還會了解出錯的位置,以及如何優化每種情況。

最後,在開始之前,我們還要處理一件事…到目前為止,我們一直專注于資源(CSS、JS 或 HTML 檔案)可進行處理之後浏覽器中會發生什麼,但忽略了從緩存或網絡中提取資源的時間。在下一課中,我們将深入研究如何細緻優化應用的網絡方面,但同時(為了更貼近現實)我們将做出以下假設:

  • 到伺服器的網絡往返(傳播延遲)将花費 100 毫秒
  • HTML 文檔的伺服器響應時間為 100 毫秒,而其他所有檔案的響應時間均為 10 毫秒

Hello World experience

<html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>Critical Path: No Style</title>
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
      </body>
    </html>
      

  

我們将從基本的 HTML 标記和單個無 CSS 或 JavaScript 的圖檔開始,這與獲得圖檔一樣簡單。現在,我們在 Chrome DevTools 中打開網絡時間軸,并檢查生成的資源瀑布流:

不出所料,HTML 檔案的下載下傳時間大約為 200 毫秒。注意,藍色線的透明部分表示浏覽器在網絡上等待(即尚未收到任何響應位元組)的時間,而實體部分則顯示收到第一個響應位元組之後完成下載下傳的時間。在上述示例中,HTML 下載下傳量極少(不足 4K),是以我們僅需單個往返過程即可提取整個檔案。是以,提取 HTML 文檔大約耗時 200 毫秒,其中一半的時間花費在等待網絡上,而另一半花費在伺服器響應上。

HTML 内容準備就緒後,浏覽器必須解析位元組、将其轉換為令牌,并建構 DOM 樹。注意,DevTools 可以友善地在底部報告 

DOMContentLoaded

 事件的時間(216 毫秒),該時間也與藍色垂直線相對應。HTML 下載下傳結束和藍色垂直線 (DOMContentLoaded) 之間的間隔是浏覽器建構 DOM 樹花費的時間,在此示例中僅為幾毫秒。

最後,請注意一些有趣的事情:我們的’趣照’沒有阻止 

domContentLoaded

 事件! 這證明,我們無需等待網頁上的每個資源即可建構呈現樹甚至繪制網頁:不是所有資源均對提供首次描繪起重要作用。事實上,就像我們将要看到的,我們談論關鍵呈現路徑時,通常談論的是 HTML 标記、CSS 和 JavaScript。圖檔不會阻止網頁的首次呈現,盡管如此,我們也應努力確定系統盡快繪制圖檔!

不過,系統會阻止圖檔上的

load

事件(亦常稱為

onload

):DevTools 在 335 毫秒時報告 onload 事件。回想一下,onload 事件标記的點是網頁所需的所有資源均已下載下傳并經過處理的點,這是加載微調控件可以在浏覽器中停止微調的點,并且該點使用瀑布流中的紅色垂直線進行标記。

DOMContentLoaded

domContentLoaded

load

onload

結合使用 JavaScript 和 CSS

我們的’Hello World experience’頁面表面看起來可能非常簡單,但我們需要做大量的工作才能使其呈現出這種效果! 不過在實踐中,我們還需要 HTML 之外的很多資源:我們可能需要 CSS 樣式表以及一個或多個添加網頁互動性的腳本。我們将兩者組合起來,看看會發生什麼:

<html>
      <head>
        <title>Critical Path: Measure Script</title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
      </head>
      <body onload="measureCRP()">
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script src="timing.js"></script>
      </body>
    </html>
      

添加 JavaScript 和 CSS 之前:

添加 JavaScript 和 CSS 之後:

添加外部 CSS 和 JavaScript 檔案額外增加了兩個針對瀑布流的請求(浏覽器幾乎會同時分派這兩個請求),目前一切順利。但請注意,現在,domContentLoaded 事件和 onload 事件之間的時間差小多了。這是怎麼回事?

  • 與純 HTML 示例不同,我們現在還需要提取并解析 CSS 檔案以建構 CSSOM,而且我們必須使用 DOM 和 CSSOM 來建構呈現樹。
  • 因為我們的網頁上還有一個阻止解析器的 JavaScript 檔案,是以在系統下載下傳并解析 CSS 檔案之前,domContentLoaded 事件将被阻止:JavaScript 可能會查詢 CSSOM,是以在執行 JavaScript 之前,我們必須阻止和等待 CSS。

如果我們使用内聯腳本代替外部腳本會怎樣?表面上看這是一個微不足道的問題,但實際上卻非常棘手。結果證明,即使将腳本直接内聯到網頁中,浏覽器得知腳本意圖的唯一可靠方式還是執行該腳本,而且正如我們了解的那樣,在 CSSOM 建構完成之前,我們不能直接内聯腳本。簡言之,内聯 JavaScript 也會阻止解析器。

不過,雖然内聯腳本會阻止 CSS,但此操作仍可以加快網頁呈現速度嗎? 如果最後一種情況很棘手,那這個問題會更棘手! 我們試着操作下,看看會發生什麼…

外部 JavaScript:

内聯 JavaScript:

我們減少了一個請求,但是 onload 和 domContentLoaded 的時間仍沒有變化,為什麼? 這與 JavaScript 是内聯的還是外部的沒有關系,因為隻要浏覽器遇到腳本代碼,它就會阻止和等待,直到 CSSOM 建構完成。另外,在我們的第一個示例中,浏覽器是同時下載下傳 CSS 和 JavaScript 的,而且下載下傳行為幾乎是在同一時間完成的。是以,在這一特定執行個體中,内聯 JavaScript 代碼沒有太大意義! 那我們就陷入僵局,沒法提高網頁呈現速度了嗎? 實際上,我們有多個不同的應對政策。

首先回想一下,所有内聯腳本均會阻止解析器,但是對于外部腳本來說,我們可以添加

async

關鍵字來取消阻止解析器。我們來取消内聯,并嘗試一下上述方法:

<html>
      <head>
        <title>Critical Path: Measure Async</title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
      </head>
      <body onload="measureCRP()">
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script async src="timing.js"></script>
      </body>
    </html>
      

阻止解析器的(外部)JavaScript:__

異步(外部)JavaScript:__

好多了! 解析 HTML 之後不久即會觸發 domContentLoaded 事件:浏覽器已得知不要阻止 JavaScript,而且因為沒有其他阻止解析器的腳本,CSSOM 建構也可以同步進行了。

或者,我們也可以嘗試另外一種方法,即同時内聯 CSS 和 JavaScript:

<html>
      <head>
        <title>Critical Path: Measure Inlined</title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <style>
          p { font-weight: bold }
          span { color: red }
          p span { display: none }
          img { float: right }
        </style>
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script>
          var span = document.getElementsByTagName('span')[0];
          span.textContent = 'interactive'; // change DOM text content
          span.style.display = 'inline';  // change CSSOM property
          // create a new element, style it, and append it to the DOM
          var loadTime = document.createElement('div');
          loadTime.textContent = 'You loaded this page on: ' + new Date();
          loadTime.style.color = 'blue';
          document.body.appendChild(loadTime);
        </script>
      </body>
    </html>      

注意:domContentLoaded 時間與前面示例中的時間沒有差別:我們沒有将 JavaScript 設定為異步,而是将 CSS 和 JS 同時内聯到網頁本身。這使得我們的 HTML 網頁更大,但好處是浏覽器無需等待提取外部資源,每個元素都已納入網頁。

如您所見,即使是非常簡單的網頁,優化關鍵呈現路徑也不是一件輕而易舉的事情:我們需要了解不同資源之間的依存關系圖,需要确定哪些資源是’重要資源’,而且我們必須在名目繁多的政策中做出選擇,找出在網頁中添加這些資源的恰當方式。該問題不是一個方案可以解決的,每個網頁都不盡相同,是以您必須按照相似的解決流程,找出最佳政策。

不過,我們可以回過頭來,看看能否找出某些正常性能模式…

性能模式

最簡單的可用網頁僅由 HTML 标記組成:無 CSS、JavaScript 或其他類型的資源。要呈現此網頁,浏覽器必須初始化請求、等待 HTML 文檔準備就緒、對其進行解析、建構 DOM,最後使其呈現在螢幕上:

<html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>Critical Path: No Style</title>
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
      </body>
    </html>      

T0 和 T1 之間的時間用于捕獲網絡和伺服器處理時間。 在最理想的情況(HTML 檔案較小)下,我們僅需一個網絡往返過程即可提取整個文檔;由于 TCP 傳輸協定的工作方式,較大的檔案可能需要多個往返過程,我們将在以後的課程中重新探讨這一主題。是以,在最理想的情況下,上述網頁具有一個往返過程(最少)關鍵呈現路徑。

現在,我們看一下帶有外部 CSS 檔案的同一網頁:

<html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
      </body>
    </html>      

再重複一下,我們需要一個網絡往返過程來提取 HTML 文檔,然後檢索到的标記告知我們還需要 CSS 檔案:這意味着,浏覽器必須傳回伺服器并擷取 CSS,然後才能在螢幕上呈現網頁。是以,該網頁最少需要兩個往返過程才能顯示,重複一下,CSS 檔案可能需要多個往返過程,是以重點在’最少’。

我們來定義将用于描述關鍵呈現路徑的詞彙:

  • 關鍵資源:可能阻止網頁首次呈現的資源。
  • 關鍵路徑長度:即往返過程數量,或提取所有關鍵資源所需的總時間。
  • 關鍵位元組:實作網頁首次呈現所需的總位元組數,是所有關鍵資源的傳輸檔案大小總和。 帶有一個 HTML 網頁的首個示例包含一項關鍵資源(HTML 文檔),關鍵路徑長度也與 1 個網絡往返過程(假設檔案較小)相等,而且總的關鍵位元組數正好是 HTML 文檔本身的傳輸大小。

現在,我們将其與’HTML + CSS’示例的關鍵路徑特征對比一下:

  • 2 種關鍵資源
  • 2 個或更多往返過程的最短關鍵路徑長度
  • 9 KB 的關鍵位元組

我們必須同時使用 HTML 和 CSS 來建構呈現樹,是以 HTML 和 CSS 均為關鍵資源:浏覽器僅在擷取 HTML 文檔之後提取 CSS,是以關鍵路徑長度最短為兩個往返過程;兩種資源加起來的關鍵位元組總量最多為 9 KB。

現在我們再向組合中添加一個額外的 JavaScript 檔案!

<html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script src="app.js"></script>
      </body>
    </html>      

我們添加了 app.js(網頁上的外部 JavaScript 資源),而且據我們目前所了解的,這是一種解析器阻止(即關鍵)資源。更糟的是,為了執行 JavaScript 檔案,我們還必須阻止并等待 CSSOM;注意,在

style.css

下載下傳和 CSSOM 建構完成之前,浏覽器将會暫停。

不過,如果我們實際檢視該網頁的’網絡瀑布流’,就會發現 CSS 和 JavaScript 請求幾乎會在同一時間初始化:浏覽器擷取 HTML,發現這兩種資源并初始化這兩個請求。是以,上述網頁具有以下關鍵路徑特征:

  • 3 種關鍵資源
  • 11 KB 的關鍵位元組

現在,我們擁有三種關鍵資源,關鍵位元組總量最多為 11 KB,但是我們的關鍵路徑長度仍然是兩個往返過程,因為我們可以同時傳輸 CSS 和 JavaScript! 了解關鍵呈現路徑的特征意味着能夠确定關鍵資源,并了解浏覽器将如何安排它們的提取時間。 我們繼續分析示例…

與網站開發者交流之後,我們意識到網頁中添加的 JavaScript 不必是阻止腳本:我們的某些分析和其他代碼不會阻止網頁呈現。了解這些後,我們就可以向腳本代碼中添加

async

屬性,以取消對解析器的阻止:

<html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script src="app.js" async></script>
      </body>
    </html>      

對腳本進行異步程式設計具有以下幾項優勢:

  • 腳本再也不會阻止解析器,也不再是關鍵呈現路徑的組成部分
  • 因為沒有其他關鍵腳本,CSS 也不需要阻止 domContentLoaded 事件
  • domContentLoaded 事件觸發得越早,其他應用邏輯開始執行的時間就越早

是以,我們經過優化的網頁恢複到了具有兩種關鍵資源(HTML 和 CSS)、具有兩個往返過程的最短關鍵路徑長度和 9 KB 的關鍵位元組總量。

最後,假設 CSS 樣式表僅用于列印, 那會如何呢?

<html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet" media="print">
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script src="app.js" async></script>
      </body>
    </html>      

因為 style.css 資源僅用于列印,是以,浏覽器不必阻止它就可以呈現網頁。是以,隻要 DOM 建構完成,浏覽器即具有了呈現網頁的足夠資訊! 是以,該網頁僅具有一種關鍵資源(HTML 文檔),最小關鍵呈現路徑長度為一個往返過程。

信仰賦予了我們一種強大的使命感,使我們執着的前進。