quicklink原始碼淺析

Harry楊醬發表於2018-12-19

前言

這些天估計大家都陸陸續續已經聽說了 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檢查後執行的自定義過濾器,預設沒有

原始碼解讀

  1. 合併引數,並設定常量
  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 || [];
複製程式碼
  1. 設定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})

複製程式碼
  1. 預載入

    那麼預載入會做些什麼呢?

    首先它會從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;
  });
};
複製程式碼
  1. 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);
    }
  });
});
複製程式碼

擴充套件閱讀

參考

最後

推薦一下自己的個人公眾號:前端精讀(每日定時推送一篇前端好文)

前端每日精讀

相關文章