【前端性能優化指南】3 - 如何加速頁面解析與處理?

🔙 上一篇 - 如何加快請求速度?javascript

在上一站中,咱們簡單介紹了服務端處理與響應,到目前爲止咱們已經經歷了不少環節,也已經有了許多「性能優化的武器」。像是css

  • 利用各級緩存進行優化
  • 提早進行 DNS 查詢或創建鏈接等方式加速請求
  • 在服務端避免沒必要要的耗時
  • ……

不過,不要掉以輕心,後續仍然有大量的工做等待咱們來優化。下面就到了客戶端接收響應的階段了。html

主要工做

這一階段瀏覽器須要處理的東西不少,爲了更好地理解性能優化,咱們主要將其分爲幾個部分:前端

  • 頁面 DOM 的解析;
  • 頁面靜態資源的加載,包括了頁面引用的 JavaScript/CSS/圖片/字體等;
  • 靜態資源的解析與處理,像是 JavaScript 的執行、CSSOM 的構建與樣式合成等;

大體過程就是解析頁面 DOM 結構,遇到外部資源就加載,加載好了就使用。可是因爲這部分的內容比較多,因此在這一節裏咱們重點關注頁面的解析(其餘部分在寫一節中介紹)。java

1. 注意資源在頁面文檔中的位置

咱們的目標是收到內容就儘快解析處理,頁面有依賴的資源就儘快發送請求,收到響應則儘快處理。然而,這個美好的目標也有可能會被咱們不當心破壞。git

JavaScript 腳本和 CSS 樣式表在關於 DOM 元素的屬性,尤爲是樣式屬性上都有操做的權利。這就像是一個多線程問題。服務端多線程編程中常常經過鎖來保證線程間的互斥。回到我們的前端,如今也是兩方在競爭同一個資源,顯然也是會有互斥的問題。這就帶來了 DOM 解析、JavaScript 加載與執行、CSS 加載與使用之間的一些互斥關係。github

僅僅看 DOM 與 CSS 的關係,則以下圖所示:算法

pipeline for dom and css

HTML 解析爲 DOM Tree,CSS 解析爲 CSSOM,二者再合成 Render Tree,並行執行,很是完美。然而,當 JavaScript 入場以後,局面就變了:編程

pipeline for dom and css with js

根據標準規範,在 JavaScript 中能夠訪問 DOM。所以當遇到 JavaScript 後會阻塞 DOM 的解析。於此同時,爲避免 CSS 與 JavaScript 之間的競態,CSSOM 的構建會阻塞 JavaScript 的腳本執行。總結起來就是 ——瀏覽器

JavaScript 會阻塞 DOM 構建,而 CSSOM 的構建又回阻塞 JavaScript 的執行。

因此這就是爲何在優化的最佳實踐中,咱們基本都推薦把 CSS 樣式表放在 <head> 之中(即頁面的頭部),把 JavaScript 腳本放在 <body> 的最後(即頁面的尾部)。

關於這部分的一些解釋能夠看這篇文章[1]

2. 使用 defer 和 async

上面提到了,當 DOM 解析遇到 JavaScript 腳本時,會中止解析,開始下載腳本並執行,再恢復解析,至關因而阻塞了 DOM 構建。

那除了將腳本放在 body 的最後,還有什麼優化方法麼?是有的。

可使用 deferasync 屬性。二者都會防止 JavaScript 腳本的下載阻塞 DOM 構建。可是二者也有區別,最直觀的表現以下:

async & defer

defer 會在 HTML 解析完成後,按照腳本出現的次序再順序執行;而 async 則是下載完成就當即開始執行,同時阻塞頁面解析,不保證腳本間的執行順序。

根據它們的特色,推薦在一些與主業務無關的 JavaScript 腳本上使用 async。例如統計腳本、監控腳本、廣告腳本等。這些腳本通常都是一份獨立的文件,沒有外部依賴,不須要訪問 DOM,也不須要有嚴格的執行時機限制。在這些腳本上使用 async 能夠有效避免這些非核心功能的加載影響頁面解析速度。

3. 頁面文檔壓縮

HTML 的文檔大小也會極大影響響應體下載的時間。通常會進行 HTML 內容壓縮(uglify)的同時,使用文本壓縮算法(例如 gzip)進行文本的壓縮。關於資源壓縮這一塊,在下一節的內容中還會再詳細進行介紹。


說一句題外話,你知道與頁面解析密切相關的 DOMContentLoaded 事件什麼時候觸發麼?interactive/complete 等 readyState 具體表明什麼麼?若是不太瞭解能夠從HTML spec[2]裏看。

用原話來講就是:

Returns "loading" while the Document is loading, "interactive" once it is finished parsing but still loading subresources, and "complete" once it has loaded.

The readystatechange event fires on the Document object when this value changes.

The DOMContentLoaded event fires after the transition to "interactive" but before the transition to "complete", at the point where all subresources apart from async script elements have loaded.


好了,在這一站咱們又瞭解了頁面的解析過程及其優化。

正如開頭所說,其實解析頁面、加載資源、使用資源是三個緊密相關的過程。在這裏咱們主要着眼於頁面的解析,而在「前端性能優化之旅」的下一站,咱們則會一塊兒來涉足到這部分的其餘諸多優化點中。

下一站 - 頁面靜態資源[TODO] 🔜


「性能優化」系列內容

  1. 帶你全面掌握前端性能優化 🚀

  2. 如何利用緩存減小遠程請求?

  3. 如何加快請求速度?

  4. 如何加速頁面解析與處理?(本文)

  5. 靜態資源優化的整體思路是什麼?

    5.1. 如何針對 JavaScript 進行性能優化?

    5.2. 如何針對 CSS 進行性能優化?

    5.3. 圖片雖好,但也會帶來性能問題

    5.4. 字體也須要性能優化麼?

    5.5. 如何針對視頻進行性能優化?

  6. 如何避免運行時的性能問題?

  7. 如何經過預加載來提高性能?

  8. 尾聲

目前內容已所有更新至 ✨ fe-performance-journey ✨ 倉庫中,陸續會將內容同步到掘金上。若是但願儘快閱讀相關內容,也能夠直接去該倉庫中瀏覽。


參考資料

  1. Deciphering the Critical Rendering Path
  2. HTML5 spec: current-document-readiness
  3. Async Defer — JavaScript Loading Strategies
  4. Speed up Google Maps(and everything else) with async & defer
  5. HTML5 spec: parse HTML (the end)
相關文章
相關標籤/搜索