前言
這些天估計大家都陸陸續續已經聽說了 GoogleChromeLabs/quicklink 這個專案了,它由 Google 公司著名開發者 Addy Osmani 發起,實現了在空閒時間預獲取頁面可視區域內的連結,從而加快後續載入速度,從而來做到後續頁面秒開都功能。
工作原理
Quicklink 通過以下方式加快後續頁面的載入速度:
- 檢測視區中的連結(使用 Intersection Observer )
- 等待瀏覽器空閒(使用 requestIdleCallback )
- 檢查使用者是否處於慢速連線(使用 navigator.connection.effectiveType)或啟用了省流模式(使用 navigator.connection.saveData)
- 預獲取視區內的 URL(使用或 XHR)。 可根據請求優先順序進行控制(若支援 fetch() 可進行切換)。
觸發條件
如果使用者的有效連線型別
和資料保護程式首選項表明它有用
的時候,
如果存在urls,則預取一系列URL,或者檢視document
的視口內連結。 如果進入視窗,就開始預載入
API
quicklink 接受帶有以下引數的 option 物件(可選):
- el:指定需要預獲取的 DOM 元素視區
- urls:預獲取的靜態 URL 陣列(若此配置非空,則不會檢測視區中 document 或 DOM 元素的連結)
- timeout:為 requestIdleCallback 設定的超時整數。 瀏覽器必須在此之前進行預獲取(以毫秒為單位), 預設取 2 秒。
- timeoutFn:指定超時處理函式。 預設為 requestIdleCallback。 也可以替換為 networkIdleCallback 等自定義函式(github.com/pastelsky/n… demo)
- priority:布林值,指定 fetch 的優先順序。 預設為 false。 若配置為 true 將會嘗試使用 fetch() API(而非 rel = prefetch)
- origins:允許預取的URL主機名字串的陣列。預設為相同的域源,可防止任何跨源請求。
- ignores:在origin檢查後執行的自定義過濾器,預設沒有
原始碼解讀
- 合併引數,並設定常量
options = Object.assign({
timeout: 2e3,
priority: false,
timeoutFn: requestIdleCallback,
el: document,
}, options);
observer.priority = options.priority;
const allowed = options.origins || [location.hostname];
const ignores = options.ignores || [];
複製程式碼
-
設定requestIdleCallback的callback和瀏覽器呼叫callback的最後期限
這裡回撥函式提供了兩種策略:
- 如果引數中有urls,則只將urls所有的連結進行預載入,不會對dom下的其他連結進行預載入
- 如果引數中沒有urls,根據options的el來遍歷其下的所有a標籤,通過Intersection Observer來監控
如果符合options.origins規則,且不符合options.ignores規則,將其放入預載入的列表
toPrefetch
中, 可以通過不傳options.origins來匹配所有
const toPrefetch = new Set();
options.timeoutFn(() => {
if (options.urls) {
options.urls.forEach(prefetcher);
} else {
Array.from(options.el.querySelectorAll('a'), link => {
// 把每一個a標籤放入觀察物件中,observer後面解釋
observer.observe(link);
if (!allowed.length || allowed.includes(link.hostname)) {
isIgnored(link, ignores) || toPrefetch.add(link.href);
}
});
}
}, {timeout: options.timeout})
複製程式碼
-
預載入
那麼預載入會做些什麼呢?
首先它會從
toPrefetch
刪除這個即將請求的url然後通過
preFetched
判斷是否已經載入過了,來減少不必要的請求。然後它會判斷當前是否為2g或者省流量模式,如果是,則不做任何操作。
接著會判斷請求型別,預設是rel = prefetch,為true的時候,將會用fetch去請求資料,並對fetch做相容。
最後,更新
preFetched
這個物件
// index.mjs
function prefetcher(url) {
toPrefetch.delete(url);
prefetch(new URL(url, location.href).toString(), observer.priority);
}
// prefetch.mjs
const preFetched = {};
function prefetch(url, isPriority, conn) {
if (preFetched[url]) {
return;
}
if (conn = navigator.connection) {
if ((conn.effectiveType || '').includes('2g') || conn.saveData) return;
}
return (isPriority ? highPriFetchStrategy : supportedPrefetchStrategy)(url).then(() => {
preFetched[url] = true;
});
};
複製程式碼
-
Intersection Observer
通過新建一個觀察者,來觀察放入觀察的a標籤,當a標籤進入視窗的時候,則開始預載入這個連結
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const url = entry.target.href;
if (toPrefetch.has(url)) prefetcher(url);
}
});
});
複製程式碼
擴充套件閱讀
- 你應該知道的requestIdleCallback
- quicklink 為你的頁面實現秒開
- 使用 Intersection Observer 實現圖片延遲載入
- 使用交叉點觀察器延遲載入影象以提高效能
- 你應該知道的requestIdleCallback
參考
最後
推薦一下自己的個人公眾號:前端精讀(每日定時推送一篇前端好文)