評價頁面效能好壞的核心之一就是頁面的載入速度,而頁面載入速度的關鍵就是頁面資源的載入。本文將從瀏覽器瀏覽器頁面資源載入過程展開分析,來引出頁面關鍵請求路徑的概念,並給出如何優化該關鍵請求路徑的一些方法。 下面相關內容,都是以chrome瀏覽器為例來進行介紹的。不同瀏覽器之間,可以會略有差異,但基本過程是一致的。
瀏覽器載入資源過程
首先丟擲兩個問題:
- 瀏覽器如何知道應該載入哪些資源?
- 瀏覽器是什麼順序來載入這些資源? 當瀏覽器截獲到一個頁面請求後,將會按照順序做如下圖所示的4件事。
- 首先會將所有需要載入的資源進行分類。
- 然後根據瀏覽器相關的安全策略,來決定資源的載入許可權。
- 接著對各個資源的載入優先順序進行計算和排序。
- 最後一步,根據載入優先順序順序來載入資源。
第一步:資源分類
chrome瀏覽器會將資源分為14類,如下表所示。
型別 | 介紹 |
---|---|
kMainResource | 即主資源,html頁面檔案資源就屬於該型別 |
kImage | 各種圖片資源 |
kCSSStyleSheet | 顧名思義,就是層疊樣式表css資源 |
kScript | 指令碼資源,例如js資源 |
kFont | 字型資源,例如網頁中常用的字型集.woff資源 |
kRaw | 混合型別資源,最常見的ajax請求就屬於這類資源 |
kSVGDocument | SVG可縮放向量圖形檔案資源 |
kXSLStyleSheet | 擴充套件樣式表語言XSLT,是一種轉換語言,關於該型別可以查閱w3c XSL來了解 |
kLinkPrefetch | HTML5頁面的預讀取資源(Link prefetch),例如dns-prefetch。下面會有詳細介紹 |
kTextTrack | video的字幕資源,- 即<track> 標籤 |
kImportResource | HTML Imports,將一個HTML檔案匯入到其他HTML文件中,例如<link href="import/post.html" rel="import" /> 。詳細瞭解請查閱相關文件。 |
kMedia | 多媒體資源,video or audio都屬於該類資源 |
kManifest | HTML5 應用程式快取資源 |
kMock | 預留的測試型別 |
第二步:安全策略檢查
網頁安全政策(Content Security Policy,縮寫 CSP)是由瀏覽器提供的一種白名單制度。開發者通過配置,來告訴瀏覽器各類外部資源的載入和執行限制,來提高網頁的安全性。一種最常用的應用就是通過限制非信任域名指令碼的載入來預防XSS攻擊。 可以通過兩種方式來配置CSP。 第一種,就是通過頁面HTTP請求頭的Content-Security-Policy欄位來限制。如下圖所示,這是www.google.com頁面的請求頭:
第二種是,通過<meta>
標籤來設定。<meta>
是以key-value的方式來進行配置的。下面以幾個具體的應用例子來介紹。
- 用於預防XSS:
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; style-src nos.netease.com kaola.com;">
複製程式碼
上面的script-src
代表指令碼資源;style-src
代表樣式資源;'self'
代表只信任當前域名下的外來資源,其他域下的資源全部會被攔截;nos.netease.com kaola.com
代表信任nos.netease.com和kaola.com這兩個域名下的資源。
所以上面的標籤的意義就是:對於指令碼資源只信任本域下的,對於樣式資源,除了本域還會載入nos.netease.com和kaola.com這兩個域名下的。
- 用於站點請求協議升級過渡(http轉https):
<meta http-equiv="Content-Secur****ity-Policy" content="upgrade-insecure-requests">
複製程式碼
上面的upgrade-insecure-requests
的意義,就如同字面意思一樣:升級所有非安全請求。當加了這個meta標籤以後,瀏覽器會將https頁面中的所有htttp請自動升級到https。例如,當我們需要進行全站http轉https改造時,對於原有的大量http資源會直接強制以https或wss等SSL加密形式傳送請求而不會報錯。當然如果資源伺服器不支援https等SSL加密,那麼該資源還是不會載入。
- 用於阻止Mixed Content:
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
複製程式碼
混合內容(Mixed Content)就是第2個例子所說的,在https站點中,進行的http請求。這類在安全連結中混合了非安全請求內容就叫混合內容。出現這類請求時,我們可以在瀏覽器控制檯中找到對應的警告資訊,如下圖所示。
混合內容會降低HTTPS網站的安全性和使用者體驗。不過讓人略感放心的是,瀏覽器對於可能對安全性造成較大威脅的資源型別的混合模式請求都會直接攔截報錯,例如指令碼資源,如下圖所示。但對於圖片、音訊、視訊等資源只會警告,但不會阻止其載入。 對於安全性要求極高的網站,可以通過上面的標籤來阻止所以型別的非安全連結請求,這樣包括圖片、音訊、視訊等資源也將會被攔截報錯。 當然對於Content-Security-Policy的設定還有很多其他作用,大家可以通過MDN來做進一步瞭解。第三步:資源優先順序計算
資源的優先順序被分為5級。不同資料上,對這5級的命名描述上可能有所不同。主要是因為資料本身可能是從網路層面,瀏覽器核心或者使用者端控制檯顯示這三個方向中的某一個來說的。這三個方向雖然對這5級的命名不同,但都是一一對應的。 網路層面,5級分別為:Highest、Medium、Low、Lowest、Idle; 瀏覽器核心,5級分別為:VeryHigh、High、Medium、Low、VeryLow; 使用者端控制檯顯示,5級分別為:Highest、High、Medium、Low、Lowest;
下面是以瀏覽器核心作為研究方向,來介紹瀏覽器的資源優先順序計算過程:
- 第一步,根據資源的型別來設定預設優先順序。 對於每一類資源瀏覽器都有一個預設的載入優先順序規則:
- html、css、font這三種型別的資源優先順序最高;
- 然後是preload資源(通過
<link rel=“preload">
標籤預載入)、script、xhr請求; - 接著是圖片、語音、視訊;
- 最低的是prefetch預讀取的資源。
- 第二步,根據一定的實際規則,對優先順序進行調整。 初始優先順序設定好以後,瀏覽器會根據資源的實際屬性和位於文件中的位置等方面,對優先順序進行調整,來確定出最終的載入優先順序順序。對於幾個常見資源型別的調整規則如下:
- 對於XHR請求資源:將同步XHR請求的優先順序調整為最高。 XHR請求可以分為同步請求和非同步請求,瀏覽器會把同步請求的優先順序提升到最高階,以便儘早獲取資料、加快頁面的顯示。
- 對於圖片資源:會根據圖片是否在可見檢視之內來改變優先順序。 圖片資源的預設優先順序為Low。現代瀏覽器為了提高使用者首屏的體驗,在渲染時會計算圖片資源是否在首屏可見檢視之內,在的話,會將這部分視口可見圖片(Image in viewport)資源的優先順序提升為High。
- 對於指令碼資源:瀏覽器會將根據指令碼所處的位置和屬性標籤分為三類,分別設定優先順序。
首先,對於新增了defer/async屬性標籤的指令碼的優先順序會全部降為Low。
然後,對於沒有新增該屬性的指令碼,根據該指令碼在文件中的位置是在瀏覽器展示的第一張圖片之前還是之後,又可分為兩類。在之前的
(標記early**)
它會被定為High優先順序,在之後的(標記late**)
會被設定為Medium優先順序。 下圖總結了資源優先順序計算後各類資源的優先順序情況,其中特別將上面講的三種常見資源的情況框了出來。紅框框中的為指令碼型別、紫框的為圖片型別、藍框為XHR請求。圖片來源點此。
第四步:按照上面計算的安全策略和優先順序來載入或阻塞資源。
關鍵請求鏈和優化
上面詳細介紹了瀏覽器的資源載入過程,其中核心在於資源的載入優先順序的計算。我們可以通過優化資源的載入優先順序順序,來有效提高頁面的載入響應速度。
首先來介紹下關鍵請求鏈(Critical-Request-Chains)的概念。可視區域渲染完畢(首屏),並對於使用者來說可用時,必須載入的資源請求佇列,就叫做關鍵請求鏈。這樣,我們可以通過關鍵請求鏈,來確定優先載入的資源以及載入順序,以實現瀏覽器儘可能快地載入頁面。
如何查詢頁面的關鍵請求鏈
- 通過首屏快照獲取關鍵image資源。 如下圖所示,我們通過首屏快照,來獲取首屏所要載入的圖片資源。(紅框內)
- 通過LightHouse外掛獲取關鍵請求鏈中的關鍵js和css資源。 LightHouse詳細的使用方法可以通過點選此處來了解。通過執行該外掛最終可以生成一個報告,裡面包含了有關該頁面效能的全方面報告和建議。其中有關關鍵請求鏈的報告如下圖所示:
- 通過瀏覽器控制檯檢視各個請求的優先順序 開啟Chrome控制檯,切換到Network tab下,就可以檢視資源的優先順序(Priority)。如果沒有Priority一欄,可以右鍵在下拉選單中勾選Priority即可。如下圖所示:
優化關鍵請求鏈
優化關鍵請求鏈有很多方法,這裡主要介紹兩種。
-
第一種:利用Preload和Prefetch。
這兩個標籤在文章前面的介紹中就已經有所介紹,它們都屬於預載入效能優化技術。對於開發人員,我們可能比瀏覽器更加了解我們的應用。從而可以使用該技術來預先告知瀏覽器某些資源可能在將來會被使用到,讓瀏覽器對這部分資源進行提前載入。 Preload:
<link rel="preload" href="test.jpg"> 複製程式碼
Prefetch: Prefetch包括資源預載入、DNS預解析、http預連線和頁面預渲染。
資源預載入:<link rel="prefetch" href="test.css"> DNS預解析:<link rel="dns-prefetch" href="//haitao.nos.netease.com"> http預連線:<link rel="prefetch" href="//www.kaola.com"> 將建立對該域名的TCP連結 頁面預渲染:<link rel="prerender" href="//m.kaola.com"> 將會預先載入連結文件的所有資源 複製程式碼
那麼Prefetch和Preload有什麼區別呢? 具體來講,Preload來告訴瀏覽器預先請求當前頁需要的資源,從而提高這些資源的請求優先順序。比如,對於那些本來請求優先順序較低的關鍵請求,我們可以通過設定Prefetch來提升這些請求的優先順序。 Prefetch來告訴瀏覽器使用者將來可能在其他頁面(非本頁面)可能使用到的資源,那麼瀏覽器會在空閒時,就去預先載入這些資源放在http快取內,最常見的dns-prefetch。比如,當我們在瀏覽A頁面,如果會通過A頁面中的連結跳轉到B頁面,而B頁面中我們有些資源希望儘早提前載入,那麼我們就可以在A頁面裡新增這些資源Prefetch,那麼當瀏覽器空閒時,就會去載入這些資源。 所以,對於那些可能在當前頁面使用到的資源可以利用Prefetch,而對一些可能在將來的某些頁面中被使用的資源可以利用Preload。如果從載入優先順序上看,Prefetch會提升請求優先順序;而Preload會把資源的優先順序放在最低,當瀏覽器空閒時才去預載入。
- 潑盆冷水: 既然Prefetch和Preload作用如此強大,我們是否可以放心使用呢?但實際上,除了dns-prefetch,其他的相容性都十分堪憂。特別是在Safari上,由於蘋果公司對安全性的苛刻要求,基本上對這些預載入技術都未實現支援。 Preload的支援性如下圖所示,發現新版chrome支援較好,但Safari全軍覆沒。 dns-prefetch支援性還不錯。 Prefetch同樣的Safari全軍覆沒。
-
第二種:利用LocalStorage。 既然第一種的預載入技術來進行資源快取的支援性較差,那麼通常可以利用LocalStorage來對部分請求的資料和結果進行快取,省去傳送http請求所消耗的時間,從而提高網頁的響應速度。 這類做法在移動端應用已經十分廣泛。下面分別介紹鵝、貓、狗三家頁面是如何利用LS來進行請求快取的。
-
微信:利用LS來對js檔案進行快取。 如下圖所示,用瀏覽器開啟一篇微信公眾賬號文章,開啟控制檯,發現Network裡竟然一個js檔案都不需要載入?一臉懵逼!
切到LS才譁然大悟,原來所有的JS都藏在這裡了! 微信就是利用了這種技巧來快取關鍵路徑裡的js資源,從而大大加快了頁面訪問速度。 當然,實際實現起來,並不像表面看得那樣,第一次訪問時將js放到LS裡,每次進來取出來執行這麼簡單,最核心的其實是需要設計一套快取更新機制。首先我們對於快取的js檔案要通過字尾來設定獨一無二的版本標識;其次,每次後端需要傳一份資源配置檔案,前端會根據這個配置檔案來和LS中快取的檔案進行版本標識匹配,從而決定是利用LS快取,還是重新發請求更新資源。例如,微信中的這個配置檔案就是通過moon_map來同步輸出給前端的,如下圖所示: -
天貓:利用LS來對關鍵的XHR非同步請求進行快取。 以天貓超市首頁為例: 如下圖,檢視LS,發現其對首屏中的輪播和10個分類入口的資料進行了快取。
上面的json內容,格式化後,發現其中包含banner和flowIcons這兩個屬性,裡面的資料分別對應的就是輪播和分類入口的資料。這樣就可以大大提升首屏的渲染展示時間。 -
京東:利用LS來對非關鍵請求進行快取。 以PC版的京東首頁為例。京東反向思維,另闢奇徑地採用了另一種方式來利用LS。那就是把非關鍵請求剝離出來存放在LS內。 具體來說,對於首屏資料,還是正常載入和展示。但為了非首屏資料的載入和渲染會阻塞和搶佔資源,從而影響首屏頁面渲染。所以將非首屏資源的HTML/CSS等資源抽出來放在LS內,當頁面滾動到可視區域時再去LS中獲取資料,插入到dom中。這點很類似於現在的模組懶載入。如下圖所示,每個LS裡都包含了一個模組所需要的HTML/CSS的資源。
PS:廣告一波,網易考拉前端招人啦~有興趣的戳我投遞簡歷
END
參考資料:
- 從Chrome原始碼看瀏覽器如何載入資源:https://zhuanlan.zhihu.com/p/30558018。
- Preload,Prefetch 和它們在 Chrome 之中的優先順序:https://yq.aliyun.com/articles/226240
- 聊聊瀏覽器資源載入優化:http://qingbob.com/let-us-talk-about-resource-load/
- 關鍵請求:http://www.zcfy.cc/article/the-critical-request-css-tricks-3843.html
- 其他:相關內容的MDN文件及Google Web Develop文件。