在前端性能優化樹上有不少值得展開的話題,從輸入 URL 到頁面加載完成發生了什麼 這一道經典的面試題就涉及到不少內容,但前端主要關注的部分仍是 瀏覽器解析響應的內容並渲染展現給用戶 這一步,本文將會詳細分析這一步的具體過程並在分析的過程當中理解該如何作性能優化。css
首先介紹一個名詞 CRP,即 關鍵渲染路徑 (Critical Rendering Path)(後文統一以 CRP 指代):html
關鍵渲染路徑是瀏覽器將 HTML CSS JavaScript 轉換爲在屏幕上呈現的像素內容所經歷的一系列步驟。前端
當咱們請求某個 URL 之後,瀏覽器得到響應的數據並將全部的標記轉換到咱們在屏幕上所看到的 HTML
,有沒有想過這中間發生了什麼?git
瀏覽器會遵循定義好的完善步驟,從處理 HTML 和構建 DOM 開始:github
StartTag: HTML
StartTag:head
Tag: meta
EndTag: head
這樣的令牌 ,整個瀏覽由令牌生成器來完成。HTML
head
這些節點對象,起始和結束令牌代表了節點之間的關係。DOM 是一個樹結構,表示了 HTML 的內容和屬性以及各個節點之間的關係。web
好比如下代碼:面試
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
複製代碼
最終就轉成下面的 DOM 樹:瀏覽器
瀏覽器如今有了頁面的內容,那麼該如何展現這個頁面自己呢?緩存
與轉換 HTML 相似,瀏覽器首先會識別 CSS 正確的令牌,而後將這些令牌轉成 CSS 節點,子節點會繼承父節點的樣式規則,這就是層疊規則和層疊樣式表。性能優化
好比上面的 HTML 代碼有如下的 CSS :
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
複製代碼
最終就轉成下面的 CSSOM 樹:
這裏須要特別區分的是,DOM 樹會逐步構建來使頁面更快地呈現,可是 CSSOM 樹構建時會阻止頁面呈現。
緣由很簡單,若是 CSSOM 樹也能夠逐步呈現頁面的話,那麼以後新生成的子節點樣式規則有可能會覆蓋以前的規則,這就會致使頁面的錯誤渲染。
讓咱們來作一個思考題,請看如下的 HTML 代碼:
<div>
<h1>H1 title</h1>
<p>Lorem...</p>
</div>
複製代碼
對於如下兩個樣式規則,哪一個樣式規則會渲染得更快?
h1 { font-size: 16px }
div p { font-size: 12px }
複製代碼
直覺上很容易以爲第二個規則是更具體的,應該會渲染更快,但實際上偏偏相反:
那麼到如今爲止,DOM 樹包含了頁面的全部內容,CSSOM 樹包含了頁面的全部樣式,接下來如何將內容和樣式轉成像素顯示到屏幕上呢?
瀏覽器會從 DOM 樹的根部開始看有沒有相符的 CSS 規則,若是有的話就將節點和樣式複製到渲染樹上,沒有的話就只將節點複製過來,而後繼續向下遍歷。
特別要注意的是,渲染樹最重要的特性是只捕獲可見內容 :
display: none
,表示這個節點不該該呈現,則這個節點和其子項都會直接跳過。好比如下將 DOM 樹和 CSSOM 樹合併成渲染樹的結果:
如今咱們已經有了渲染樹,接下來要作的是肯定元素在頁面上的位置。
咱們考慮如下的代碼:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critial Path: Hello world!</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>
複製代碼
瀏覽器在渲染時會將這裏父 div 的寬度設置成 body 的 50%,將子 div 的寬度設成父 div 的 50%,那麼這裏 body 的寬度是如何肯定的?
注意咱們在 meta 標籤中設置了一行代碼:
<meta name="viewport" content="width=device-width,initial-scale=1">
複製代碼
咱們在實際進行自適應網頁設計時都會加上這行代碼表示佈局視口的寬度等於設備的寬度,所以呈現出來就是這樣:
最後一步就是將全部準備好的內容 繪製 到頁面上。
任什麼時候候咱們想要更新渲染樹時,可能都會從新進行佈局和繪製這一過程,瀏覽器自己會採起各類智能的功能嘗試從新繪製最低請求區域,但具體仍是取決於咱們向渲染樹應用了哪一種類型的更新。
在談優化以前,咱們先定義一下用來描述 CRP 的詞彙:
- 關鍵資源: 可能阻止網頁首次渲染的資源。
- 關鍵路徑長度: 獲取全部關鍵資源所需的往返次數或總時間。
- 關鍵字節: 實現網頁首次渲染所需的總字節數,等同於全部關鍵資源傳送文件大小的總和。
結合咱們談過的步驟,咱們着重會考慮的優化策略是在合成渲染樹以前。
首先咱們能夠優化 DOM,具體體如今如下幾步:
而後是優化 CSSOM,縮小、壓縮以及緩存一樣重要,對於 CSSOM 咱們前面重點提過了它會阻止頁面呈現,所以咱們能夠從這方面考慮去優化,讓咱們看下面的代碼:
body { font-size: 16px }
@media screen and (orientation: landscape) {
.menu { float: right }
}
@media print {
body { font-size: 12px }
}
複製代碼
當瀏覽器遇到 CSS 時,會阻止呈現頁面直到 CSSOM 解析完畢,可是對於一些特定場合纔會運用的 CSS (好比上面兩個媒體查詢),瀏覽器會依舊請求,但不會阻塞渲染了,這也是爲何咱們有時會將 CSS 文件拆分到不一樣的文件,上面的樣式表聲明能夠優化成這樣:
<link href="style.css" rel="stylesheet">
<link href="landscape.css" rel="stylesheet" media="orientation:landscape">
<link href="print.css" rel="stylesheet" media="print">
複製代碼
當咱們用 PageSpeed Insights 檢測咱們的網站時,常常出現的一條就是 建議減小關鍵 CSS 元素數量 。
Google 官方文檔 也建議: 當咱們聲明樣式表時,請密切關注媒體查詢的類型,它們極大地影響了 CRP 的性能 。
接下來讓咱們考慮 JavaScript
外部依賴能夠優化的地方,再看下面的代碼:
<p>
Awesome page
<script src="write.js"></script>
is awesome
</p>
複製代碼
當瀏覽器遇到 script 標記時,會阻止解析器繼續操做,直到 CSSOM 構建完畢,JavaScript
纔會運行並繼續完成 DOM 構建過程,對於 JavaScript
依賴的優化,咱們最經常使用的一種方法是當網頁加載完成,瀏覽器發出 onload 事件後再去執行腳本(或者直接放在底部),但實際上還有更簡單的策略:
async
: 當咱們在 script 標記添加 async
屬性之後,瀏覽器遇到這個 script 標記時會繼續解析 DOM,同時腳本也不會被 CSSOM 阻止,即不會阻止 CRP。defer
: 與 async
的區別在於,腳本須要等到文檔解析後( DOMContentLoaded
事件前)執行,而 async
容許腳本在文檔解析時位於後臺運行(二者下載的過程不會阻塞 DOM,但執行會)。async
。這裏給出一個參考圖:
瀏覽器還有一個特殊的流程,叫作預加載掃描器,它會提早掃描文檔並發現關鍵的 CSS 和 JS 資源來下載,這個過程不會阻塞渲染,想詳細瞭解它的原理能夠瀏覽這篇文章 How the Browser Pre-loader Makes Pages Load Faster,實際的應用可瀏覽 前端性能優化之關鍵路徑渲染優化
總結一下,爲了首屏最快地渲染,咱們一般會採起下列步驟:
更詳細的優化建議能夠閱讀 PageSpeed Rules and Recommendations