預加載功能詳解


前言

最近在研究 vue-cli 3.0生成的工程,在構建后生成的 index.html里面發現了下面這種用法:

 

<link as=style href=/css/app.f60416c7.css rel=preload>
<link as=script href=/js/app.69189fdd.js rel=preload>

這就觸到了本人的知識盲區了,本着掃盲的目的,研究了下 link 標簽,發現這個小東西功能還是挺強大的,上面的就是為了實現預加載功能,懂點兒英文的,一看見preload 就大致知道了。

 

之前也有預加載技術,像 prefetch,subresource 等,關於這兩者和 preload 的區別,這是另外的話題了, 感興趣的可以自己搜一下,不想搜的,你只要知道這兩個跟 preload 相比弱的一逼就行了,就是 prefetch 瀏覽器兼容性方面稍微好一點點,這三個也各有偏重和應用場景,就不詳細介紹了,下面我們就詳細展開preload 這塊。

功能介紹:

preload 是一項新的 web 標准,旨在提高性能,讓 FE 對加載的控制更加粒度化。它讓開發者有自定義加載邏輯的能力,免受基於腳本的加載器所帶來的性能損耗。

preload 一個基本的用法就是提前加載資源,盡管大多數基於標記語言的資源能被瀏覽器的預加載器(preloader)盡早發現,但不是所有的資源都是基於標記語言的,比如一些隱藏在 css 和 js 中的資源(字體,圖片等),當瀏覽器發現頁面需要這些資源時,重新走一遍加載執行渲染的過程,會降低用戶體驗,並且對頁面的渲染 造成延遲;

 

Preloader 簡介
HTML 解析器在創建 DOM 時如果碰上同步腳本(synchronous script),解析器會停止創建 DOM,轉而去執行腳本。所以,如果資源的獲取只發生在解析器創建 DOM時,同步腳本的介入將使網絡處於空置狀態,尤其是對外部腳本資源來說,當然,頁面內的腳本有時也會導致延遲。
 
預加載器(Preloader)的出現就是為了優化這個過程,預加載器通過分析瀏覽器對 HTML 文檔的早期解析結果(這一階段叫做“令牌化(tokenization)”),找到可能包含資源的標簽(tag),並將這些資源的 URL 收集起來。令牌化階段的輸出將會送到真正的 HTML 解析器手中,而收集起來的資源 URLs 會和資源類型一起被送到讀取器(fetcher)手中,讀取器會根據這些資源對頁面加載速度的影響進行有次序地加載。

 

預加載的好處:

  1. 讓瀏覽器提前加載指定資源(這里預加載完成后並不執行),在需要執行的時候在執行,這樣將加載和執行分開,可以不阻塞渲染和 window.onload事件。
  1. 提前預加載指定資源,特別是字體文件,不會再出現 font 字體在頁面渲染出來后,才加載完畢,然后頁面字體閃一下變成預期字體。
  1. 帶有 onload 事件,可以自定義資源在預加載完畢后的回調函數。
涉及屬性介紹:

屬性名

取值范圍

介紹

as

script:js 腳本font:字體文件style:樣式表audio:音頻video: 視頻document:將被嵌入到<frame>或<iframe>元素內部的頁面image: 圖片fetch:將要通過 fetch 和 XHR 請求獲取的資源比如jsonobject: 將被嵌入到<embed >元素內的文件worker:js 的 web worker 或 share worker

該屬性僅在 link 元素設置了rel=preload 是才能使用。規定了 link 元素要預加載的資源的類型,其取值范圍也限制了哪些資源才可被預加載。設置了此屬性使瀏覽器能夠:1,更精確地優化資源加載優先級。2,匹配未來的加載需求,在適當的情況下,重復利用同一資源。3,為資源應用正確的內容安全策略。4,為資源設置正確的 Accept 請求頭。

href

<url>

指定要加載資源的 URL,可以使絕對地址也可以是相對地址

rel

preload(當前功能相關)

此屬性用於指明被鏈接的資源相對於當前頁面的關系。屬性值一定是被空格分開的鏈接類型值。這個屬性最常見的取值是:stylesheet,表明被連接資源對當前文檔來說是一個層疊樣式表。當前取值 preload 表明鏈接資源是一個預加載的資源

type

MIME涵蓋類型

鏈接資源的 MIME 類型,在瀏覽器進行預加載到時候,這個屬性將會非常有用,瀏覽器將使用 type 屬性來判斷它是否支持這一資源類型,如果支持,將正常預加載,下載將開始,否則對其忽略。

crossorigin

 

加載字體文件的時候需要用到,詳情往下看

應用場景:
  • 包含媒體

<link>元素有一個很棒的特性是它們能夠接受一個media屬性。它們可以接受媒體類型或有效的媒體查詢作為屬性值,這將令你能夠使用響應式的預加載!

讓我們來看一個簡單的示例(可以查看Github上的源代碼在線示例):

<head>
  <meta charset="utf-8">
  <title>Responsive preload example</title>
 
        
  <link rel="preload" href="bg-image-narrow.png" as="image" media="(max-width: 600px)">
  <link rel="preload" href="bg-image-wide.png" as="image" media="(min-width: 601px)">
 
        
  <link rel="stylesheet" href="main.css">
</head>
<body>
  <header>
    <h1>My site</h1>
  </header>
 
        
  <script>
    var mediaQueryList = window.matchMedia("(max-width: 600px)");
    var header = document.querySelector('header');
 
        
    if(mediaQueryList.matches) {
      header.style.backgroundImage = 'url(bg-image-narrow.png)';
    } else {
      header.style.backgroundImage = 'url(bg-image-wide.png)';
    }
  </script>
</body>
 
        

你可以看到我們在<link>元素中包含了一個media屬性,因此,當用戶在使用較窄屏幕的設備時,較窄的圖片將會被預加載,而在較寬的設備上,較寬的圖片將被預加載。然后我們仍需要在header元素上附加合適的圖片——通過Window.matchMedia / MediaQueryList 來加以實現(可以查看Testing media queries一文來了解更多信息)。

 

  • 字體提前加載

web 字體是較晚才能被發現的關鍵資源中常見的一種。但是在用戶體驗對前端來說至關重要的現階段前端開發來說,web 字體對頁面的渲染也是至關重要。字體的引用被深埋在 css 中,即便預加載器有提前解析 css,也無法確定包含字體信息的選擇器是否會真正作用在 dom 節點上。所以為了減少 FOUT(無樣式字體閃爍,flash of unstyled text )需要預加載字體文件,有了 preload,一行代碼搞定:

 

<link rel=preload href='font.woff2' as=font type='font/woff2' crossorigin />

 

NOTE :
crossorigin 屬性在加載字體的時候是必須的,即便字體沒有跨域是在自己公司的服務器上,因為用戶代理必須采用匿名模式來獲取字體資源(為什么會這樣呢?)。
type 屬性可以確保瀏覽器只獲取自己支持的資源。

 

 

  • 動態加載,但不執行

另外一個有意思的場景也因為 preload 的出現變得可能——當你想加載某一資源但卻不想執行它。比如說,你想在頁面生命周期的某一時刻執行一段腳本,而你無法對這段腳本做任何修改,不可能為它創建一個所謂的 runNow()函數。

 

在 preload 出現之前,你能做的很有限。如果你的方法是在希望腳本執行的位置插入腳本,由於腳本只有在加載完成以后才能被瀏覽器執行,也就是說你得等上一會兒。如果采用 XHR 提前加載腳本,瀏覽器會拒絕重用這段腳本,有些情況下,你可以使用 eval 函數來執行這段腳本,但該方法並不總是行得通,也不是完全沒有副作用。

 

現在有了 preload,一切變得可能

 

var link = document.createElement("link");
link.href = "myscript.js";
link.rel = "preload";
link.as = "script";
document.head.appendChild(link);

 

上面這段代碼可以讓你預先加載腳本,下面這段代碼可以讓腳本執行

 

var script = document.createElement("script");
script.src = "myscript.js";
document.body.appendChild(script);

 

 

  • 基於標記語言的異步加載

先看代碼

 

<link rel="preload" as="style" href="asyncstyle.css" onload="this.rel='stylesheet'">

 

preload 的 onload 事件可以在資源加載完成后修改 rel 屬性,從而實現非常酷的異步資源加載。

 

腳本也可以采用這種方法實現異步加載

 

難道我們不是已經有了<script async>? <scirpt async>雖好,但卻會阻塞 window 的 onload 事件。某些情況下,你可能希望這樣,但總有一些情況你不希望阻塞 window 的 onload 。

 

舉個例子,你想盡可能快的加載一段統計頁面訪問量的代碼,但又不願意這段代碼的加載給頁面渲染造成延遲從而影響用戶體驗,關鍵是,你不想延遲 window 的 onload 事件。

 

有了preload, 分分鍾搞定。

 

<link rel="preload" as="script" href="async_script.js"
      onload="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">

 

 

  • 響應式加載

preload 是一個link,根據規范有一個media 屬性(現在 Chrome 還不支持,不過快了),該屬性使得選擇性加載成為可能。

 

有什么用處呢?假設你的站點同時支持桌面和移動端的訪問,在使用桌面瀏覽器訪問時,你希望呈現一張可交互的大地圖,而在移動端,一張較小的靜態地圖就足夠了。

 

你肯定不想同時加載兩個資源,現在常見的做法是通過 JS 判斷當前瀏覽器類型動態地加載資源,但這樣一來,瀏覽器的預加載器就無法及時發現他們,可能耽誤加載時機,影響用戶體驗和 SpeedIndex 評分。

 

怎樣才能讓瀏覽器盡可能早的發現這些資源呢?還是 Preload!

 

通過 Preload,我們可以提前加載資源,利用 media 屬性,瀏覽器只會加載需要的資源。

 

<link rel="preload" as="image" href="map.png" media="(max-width: 600px)">
<link rel="preload" as="script" href="map.js" media="(min-width: 601px)">

 

 

  • http header 實現預加載

Preload 還有一個特性是其可以通過 HTTP 頭信息被呈現。也就是說上文中大多數的基於標記語言的聲明可以通過 HTTP 響應頭實現。(唯一的例外是有 onload 事件的例子,我們不可能在 HTTP 頭信息中定義事件處理函數。)

 

Link: <thing_to_load.js>;rel="preload";as="script"
Link: <thing_to_load.woff2>;rel="preload";as="font";crossorigin

 

這一方式在有些場景尤其有用,比如,當負責優化的人員與頁面開發人員不是同一人時(也就是說優化人員可能無法或者不想修改頁面代碼),還有一個傑出的例子是外部優化引擎(External optimization engine),該引擎對內容進行掃描並優化。

 

 

  • 瀏覽器特性檢查

前面所有的列子都基於一種假設——瀏覽器一定程度上支持 preload,至少實現了腳本和樣式加載等基本功能。但如果這個假設不成立了。一切都將是然並卵。

 

為了判斷瀏覽器是否支持 preload,我們修改了 DOM 的規范從而能夠獲知 rel 支持那些值(是否支持 rel=‘preload’)。

 

至於如何進行檢查,原文中沒有,但 Github有一段代碼可供參考。

 

var DOMTokenListSupports = function(tokenList, token) {
  if (!tokenList || !tokenList.supports) {
    return;
  }
  try {
    return tokenList.supports(token);
  } catch (e) {
    if (e instanceof TypeError) {
      console.log("The DOMTokenList doesn't have a supported tokens list");
    } else {
      console.error("That shouldn't have happened");
    }
  }
};
 
        
var linkSupportsPreload = DOMTokenListSupports(document.createElement("link").relList, "preload");
if (!linkSupportsPreload) {
  // Dynamically load the things that relied on preload.
}

 

瀏覽器兼容性:

caniuse.com 網站上顯示瀏覽器版本支持情況如下,目前還是比較高版本的瀏覽器會支持此功能,不過大家也不要擔心,在不支持的瀏覽器環境中,這部分標簽會被忽略,可以做到平穩降級。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM