天天看點

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

衡量網站的性能的名額有很多,其中有項重要的名額就是網站的首屏時間,為此前端工程師們都是絞盡腦汁想盡辦法進行優化自己的應用,諸如像服務端渲染,懶加載,CDN 加速,ServiceWorker 等等方法,今天介紹的 preload/prefetch 是一種簡單,但卻事半功倍的優化手段。

基本用法

在網絡請求中,我們在使用到某些資源比如:圖檔,JS,CSS 等等,在執行之前總需要等待資源的下載下傳,如果我們能做到預先加載資源,那在資源執行的時候就不必等待網絡的開銷,這時候就輪到 preload 大顯身手的時候了。

preload 提前加載

preload 顧名思義就是一種預加載的方式,它通過聲明向浏覽器聲明一個需要送出加載的資源,當資源真正被使用的時候立即執行,就無需等待網絡的消耗。

它可以通過 Link 标簽進行建立:

const link = document.createElement('link');

link.rel = 'preload';

link.as = 'style';

link.href = '/path/to/style.css';

document.head.appendChild(link);

當浏覽器解析到這行代碼就會去加載 href 中對應的資源但不執行,待到真正使用到的時候再執行,另一種方式方式就是在 HTTP 響應頭中加上 preload 字段:

Link: ; rel=preload; as=style

這種方式比通過 Link 方式加載資源方式更快,請求在傳回還沒到解析頁面的時候就已經開始預加載資源了。

講完 preload 的用法再來看下它的浏覽器相容性,根據 http://caniuse.com 上的介紹:IE 和 Firefox 都是不支援的,相容性覆寫面達到 73%。

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

prefetch 預判加載

prefetch 跟 preload 不同,它的作用是告訴浏覽器未來可能會使用到的某個資源,浏覽器就會在閑時去加載對應的資源,若能預測到使用者的行為,比如懶加載,點選到其它頁面等則相當于提前預加載了需要的資源。它的用法跟 preload 是一樣的:

Link: ; rel=prefetch; as=style

講完用法再講浏覽器相容性,prefetch 比 preload 的相容性更好,覆寫面可以達到将近 80%。

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

更多細節點

當一個資源被 preload 或者 prefetch 擷取後,它将被放在記憶體緩存中等待被使用,如果資源位存在有效的緩存極緻(如 cache-control 或 max-age),它将被存儲在 HTTP 緩存中可以被不同頁面所使用。

正确使用 preload/prefetch 不會造成二次下載下傳,也就說:當頁面上使用到這個資源時候 preload 資源還沒下載下傳完,這時候不會造成二次下載下傳,會等待第一次下載下傳并執行腳本。

對于 preload 來說,一旦頁面關閉了,它就會立即停止 preload 擷取資源,而對于 prefetch 資源,即使頁面關閉,prefetch 發起的請求仍會進行不會中斷。

什麼情況會導緻二次擷取?不要将 preload 和 prefetch 進行混用,它們分别适用于不同的場景,對于同一個資源同時使用 preload 和 prefetch 會造成二次的下載下傳。

preload 字型不帶 crossorigin 也将會二次擷取! 確定你對 preload 的字型添加 crossorigin 屬性,否則他會被下載下傳兩次,這個請求使用匿名的跨域模式。這個建議也适用于字型檔案在相同域名下,也适用于其他域名的擷取(比如說預設的異步擷取)。

preload 是告訴浏覽器頁面必定需要的資源,浏覽器一定會加載這些資源,而 prefetch 是告訴浏覽器頁面可能需要的資源,浏覽器不一定會加載這些資源。是以建議:對于目前頁面很有必要的資源使用 preload,對于可能在将來的頁面中使用的資源使用 prefetch。

這将會浪費使用者的帶寬嗎?

用 “preload” 和 “prefetch” 情況下,如果資源不能被緩存,那麼都有可能浪費一部分帶寬,在移動端請慎用。

沒有用到的 preload 資源在 Chrome 的 console 裡會在 onload 事件 3s 後發生警告。

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

原因是你可能為了改善性能使用 preload 來緩存一定的資源,但是如果沒有用到,你就做了無用功。在手機上,這相當于浪費了使用者的流量,是以明确你要 preload 對象。

如何檢測 preload 支援情況?

用下面的代碼段可以檢測 是否被支援:

const preloadSupported = () => {

const link = document.createElement('link');

const relList = link.relList;

if (!relList || !relList.supports)

return false;

return relList.supports('preload');

};

不同資源浏覽器優先級

在 Chrome 46 以後的版本中,不同的資源在浏覽器渲染的不同階段進行加載的優先級如下圖所示:

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

一個資源的加載的優先級被分為五個級别,分别是:Highest 最高

High 高

Medium 中等

Low 低

Lowest 最低

從圖中可以看出:(以 Blink 為例)HTML/CSS 資源,其優先級是最高的

font 字型資源,優先級分别為 Highest/High

圖檔資源,如果出現在視口中,則優先級為 High,否則為 Low

而 script 腳本資源就比較特殊,優先級不一,腳本根據它們在檔案中的位置是否異步、延遲或阻塞獲得不同的優先級:網絡在第一個圖檔資源之前阻塞的腳本在網絡優先級中是 High

網絡在第一個圖檔資源之後阻塞的腳本在網絡優先級中是 Medium

異步/延遲/插入的腳本(無論在什麼位置)在網絡優先級中是 Low

自己網站資源優先級也可以通過 Chrome 控制台 Network 一欄進行檢視.對于使用 prefetch 擷取資源,其優先級預設為最低,Lowest,可以認為當浏覽器空閑的時候才會去擷取的資源。

而對于 preload 擷取資源,可以通過 "as" 或者 "type" 屬性來辨別他們請求資源的優先級(比如說 preload 使用 as="style" 屬性将獲得最高的優先級,即使資源不是樣式檔案)

沒有 “as” 屬性的将被看作異步請求。

與其它加載方式對比

async/defer:

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

使用 async/defer 屬性在加載腳本的時候不阻塞 HTML 的解析,defer 加載腳本執行會在所有元素解析完成,DOMContentLoaded 事件觸發之前完成執行。它的用途其實跟 preload 十分相似。你可以使用 defer 加載腳本在 head 末尾,這比将腳本放在 body 底部效果來的更好。它相比于 preload 加載的優勢在于浏覽器相容性好,從 caniuse 上看基本上所有浏覽器都支援,覆寫率達到 93%,

不足之處在于:defer 隻作用于腳本檔案,對于樣式、圖檔等資源就無能為力了,并且 defer 加載的資源是要執行的,而 preload 隻下載下傳資源并不執行,待真正使用到才會執行檔案。

對于頁面上主/首屏腳本,可以直接使用 defer 加載,而對于非首屏腳本/其它資源,可以采用 preload/prefeth 來進行加載。

HTTP/2 Server Push:

HTTP/2 PUSH 功能可以讓伺服器在沒有相應的請求情況下預先将資源推送到用戶端。這個跟 preload/prefetch 預加載資源的思路類似,将下載下傳和資源實際執行分離的方法,當腳本真正想要請求檔案的時候,發現腳本就存在緩存中,就不需要去請求網絡了。

我們假設浏覽器正在加載一個頁面,頁面中有個 CSS 檔案,CSS 檔案又引用一個字型庫,對于這樣的場景,

若使用 HTTP/2 PUSH,當服務端擷取到 HTML 檔案後,知道以後用戶端會需要字型檔案,它就立即主動地推送這個檔案給用戶端,如下圖:

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

而對于 preload,服務端就不會主動地推送字型檔案,在浏覽器擷取到頁面之後發現 preload 字型才會去擷取,如下圖:

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

對于 Server Push 來說,如果服務端渲染 HTML 時間過長的話則很有效,因為這時候浏覽器除了幹等着,做不了其它操作,但是不好的地方是伺服器需要支援 HTTP/2 協定并且服務端壓力也會相應增大。對于更多 Server Push 和 preload 的對比可以參考這篇文章:HTTP/2 PUSH(推送)與HTTP Preload(預加載)大比拼

浏覽器預解析:

現代浏覽器很聰明,就如 Chrome 浏覽器,它會在解析 HTML 時收集外鍊,并在背景并行下載下傳,它也實作了提前加載以及加載和執行分離。

prefetch 和preload_使用 Preload/Prefetch 優化你的應用

它相比于 preload 方式而言:僅限于 HTML 解析,對于 JS 異步加載資源的邏輯就無無能為力了

浏覽器不暴露 preload 中的 onload 事件,也就無法更加細粒度地控制資源的加載

使用案例提前加載字型檔案。由于字型檔案必須等到 CSSOM 建構完成并且作用到頁面元素了才會開始加載,會導緻頁面字型樣式閃動。是以要用 preload 顯式告訴浏覽器提前加載。假如字型檔案在 CSS 生效之前下載下傳完成,則可以完全消滅頁面閃動效果。

使用 preload 預加載第二屏的内容,在網頁開發中,對于非首屏部分采用懶加載是我們頁面常用的優化手段,是以我們在頁面 onload 之後可以通過 preload 來加載次屏所需要的資源,在使用者浏覽完首屏内容滾動時能夠更快地看到次屏的内容。

在頁面加載完成之後,可以分析頁面上所有的連結,判斷使用者可能會點選的頁面,分析提取下一跳頁面上所有的資源使用 prefetch 進行加載(這裡不使用 preload,因為不一定會點選),浏覽器會在空閑地時候進行加載,當使用者點選連結命中了緩存,這可以有效地提升下一頁面的首屏渲染時間。

對于商品清單頁面,在使用者滑鼠停留在某個商品的時候,可以去分析商品詳情頁所需要的資源并提前開啟 preload 加載,跟第 3 點類似,都是用來預測使用者的行為并且做出一些預加載的手段,差別在于當使用者停留在商品上時,點選命中率更高,preload 可以立即加載資源,有效提升緩存命中率。

總結

preload/prefetch 是個好東西,能讓浏覽器提前加載需要的資源,将資源的下載下傳和執行分離開來,運用得當的話可以對首屏渲染帶來不小的提升,可以對頁面互動上帶來極緻的體驗。

參考連結