前端效能優化--預載入技術
當我們談到前端的效能時,總是會提到比如合併、壓縮、快取或者在伺服器上開啟gzip之類的,目的都是為了讓頁面載入的更快。
資源預拉取(prefetch)則是另一種效能優化的技術。通過預拉取可以告訴瀏覽器使用者在未來可能用到哪些資源。
Pre-fetching會提示瀏覽器那些未來一定或可能使用到的資源,有時在當前頁面見效,有些則在未來可能開啟的頁面生效。 作為開發者,我們比瀏覽器更懂自己的應用。我們可以利用這些技術提前告知瀏覽器web中用到的核心資源。
以前這種實踐也被稱為『prebrowsing』。但這並不是一種單一的技術,實際上可以拆分成很多小點:dns-prefetch, subresource, prefetch, preconnect, 和 prerender.
DNS prefetch
DNS prefetching通過指定具體的URL來告知客戶端未來會用到相關的資源,這樣瀏覽器可以儘早的解析DNS。比如我們需要一個在example.com的圖片或者視訊檔案。在<head>就可以這麼寫:
1 |
<linkrel="dns-prefetch"href="//example.com"> |
當請求這個域名下的檔案時就不需要等待DNS查詢了。專案中有用到第三方的程式碼時這麼做尤其有益(其他的使用場景,比如當靜態資源和HTML不在一個域上,而在CDN上;又比如在重定向前可以加上DNS prefetch)。
Harry Roberts在他的
簡單的一行就能讓支援的瀏覽器提前解析DNS。也就是說在瀏覽器請求資源時,DNS查詢就已經準備好了。
這可能看起來是個非常微不足道的效能提升,而且還不是必須的–Chrome總是會做類似的處理,使用者只要在位址列敲入一部分域名,如果命中了歷史常用的網站,Chrome就會提前解析DNS、預拉取頁面。(效果確實有限,但是聊勝於無)
Preconnect
和DNS prefetch類似,preconnect不光會解析DNS,還會建立TCP握手連線和TLS協議(如果需要)。用法如下:
1 |
<linkrel="preconnect"href="http://css-tricks.com" |
Ilya Grigorik寫了一篇文章詳細說明了這種技術:
現代瀏覽器竭盡所能的嘗試預測網站可能需要哪些連結。通過提前連線,瀏覽器可以提前建立必要的通訊,消除了實際請求中DNS、TCP和TLS的耗時。不過,即使是隻能的現代瀏覽器,也沒辦法為每個網站可靠的預測所有連線。
幸運的是開發者可以告訴瀏覽器哪些通訊需要在實際請求發起前就提前建立連線。
舉個栗子: 上半張圖顯示了瀏覽器先拉html、再拉CSS並建立好CSSOM後,發現需要兩個外鏈的字型(在fonts.gstatic.com上),然後瀏覽器開始發起兩個請求,具體來說,需要對這個域進行DNS解析、TCP和TLS握手(一個建立後可以複用給另一個連線)。
1 2 |
<linkhref='https://fonts.gstatic.com'rel='preconnect'crossorigin> <linkhref='https://fonts.googleapis.com/css?family=Roboto+Slab:700|Open+Sans'rel='stylesheet'> |
下半張圖增加了上面的程式碼來從fonts.gstatic.com preconnect資源。可以看到,瀏覽器在請求CSS的同時並行的建立字型資源需要的連線,等到真正開始需要字型時立刻就開始返回資料。
更多詳細的內容可以參考Ilya Grigorik的文章。
目前只支援Firefox 39+和Chrome 46+,具體參見caniuse
Prefetch
當能確定網頁在未來一定會使用到某個資源時,開發者可以讓瀏覽器提前請求並且快取好以供後續使用。prefetch支援預拉取圖片、指令碼或者任何可以被瀏覽器快取的資源。
1 |
<linkrel="prefetch"href="image.png"> |
不同於DNS prefetch,上面的寫法可是會去請求、下載資源並且快取起來。當然也是有一些發生條件的。比如,客戶端可能會在弱網路下不去請求較大的字型檔案,Firefox則只會在瀏覽器空閒的時候prefetch資源(譯者注:這裡是MDN上對瀏覽器空閒的定義和一些FAQ,建議閱讀)。
正如Bram Stein在他的文章中指出,prefetch很適用於優化webfonts的效能。以前,字型檔案必須等DOM和CSSOM建立好後才能下載,可如果prefetch了字型,這個瓶頸就能輕鬆解決了。
注意:prefetch並沒有同域的限制
Subresource
subresource可以用來指定資源是最高優先順序的。比如,在Chrome和Opera中我們可以加上下面的程式碼:
1 |
<linkrel="subresource"href="styles.css"> |
和 "Link rel=prefetch"的語義不同,"Link rel=subresource"是一種新的連線關係。rel=prefetch指定了下載後續頁面用到資源的低優先順序,而rel=subresource則是指定當前頁面資源的提前載入。
所以,如果資源是在當前頁面需要,或者馬上就會用到,則推薦用subresource,否則還是用prefetch。
Prerender
prerender是一個重量級的選項,它可以讓瀏覽器提前載入指定頁面的所有資源。
1 |
<linkrel="prerender" href="/thenextpage.html"/> |
Steve Souders的文章詳細解釋了這個技術:
prerender就像是在後臺打開了一個隱藏的tab,會下載所有的資源、建立DOM、渲染頁面、執行JS等等。如果使用者進入指定的連結,隱藏的這個頁面就會進入馬上進入使用者的視線。Google Search多年前就利用了這個特性實現了Instant Pages功能。微軟最近也宣佈會讓Bing在IE11上用類似prerender的技術。
但是要注意,一定要在十分確定使用者回點某個連結時才用這個特性,否則客戶端就會無端的下載很多資源和渲染這個頁面。
正如任何提前的動作一樣,預判總是有一定風險出錯。如果提前的動作是昂貴的(比如高CPU、耗電、佔用頻寬),就要謹慎使用了。雖然不容易預判使用者會點進哪個頁面,但還是存在一些典型的場景:
如果使用者搜尋到了一個明顯正確的結果時,那麼這個頁面就很有可能被點入
如果使用者在登入頁面,那麼登入成功後的頁面就很可能接下來會被載入了
如果使用者在閱讀一個多頁面的文章或者有頁碼的內容時,下一頁就很可能會馬上被點選了
利用Page Visibility API可以用來防止頁面在還沒真正展示給使用者時就觸發了JS的執行。
未來:Preload
以上是已有的技術,我們再談談未來。 preload草案建議允許始終預載入某些資源,不像prefetch有可能被瀏覽器忽略,瀏覽器必須請求preload標記的資源。
1 |
<linkrel="preload"href="image.png"> |
然而,這項草案還沒有任何瀏覽器支援,不過值得關注。
總結
預判使用者的操作雖然不易,而且還需要大量的設計和測試工作,但是效能的提升是值得我們孜孜不倦的去追求的。如果我們願意試驗這些預載入技術,我們肯定能顯著地提升使用者體驗。
(譯者補一句,文章說的大部分預載入技術移動端都不支援,PC支援有限,但我們顯然應該知道這些技術的存在,並且持續的關注)