簡介
quicklink是一個js庫,可以預載入出現在視口的網頁連結,提高使用者體驗。它的載入過程如下:
1.檢測網頁中的連結是否出現在視口中,等待連結出現在視口,執行步驟2。
2.等待瀏覽器空閒後執行3。
3.判斷當前的網路連線是否是2G,如果是則停止執行,如果不是2G網路,執行步驟4。
4.預載入連結指向資源。
使用方式
參考連結https://github.com/GoogleChro…
quicklink原始碼解析
quicklink的入口函式接受傳入的配置引數,通過Object.assign函式和預設的配置選項合併。接著執行timeoutFn非同步方法,該方法接收一個回撥函式,在回撥中主要邏輯如下:
如果傳入的options引數中有urls屬性,則直接執行預載入,否則通過document.querySelectorAll方法獲取所有a標籤元素的NodeList,然後便利該元素節點列表,並監視該元素節點
observer.observe(link);
然後判斷該a元素物件的href屬性值所屬的域名是否被允許訪問,如果被允許訪問,繼續判斷該連結是否應該被忽略,判斷邏輯如下:
if (!allowed.length || allowed.includes(link.hostname)) {
// If there are any filters, the link must not match any of them
isIgnored(link, ignores) || toPrefetch.add(link.href);
}
如果連結沒有被忽略,則將該節點的href屬性值加入到toPrefetch中
const toPrefetch = new Set();
toPrefetch.add(link.href);
總的程式碼邏輯如下
export default function (options) {
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 || [];
options.timeoutFn(() => {
// If URLs are given, prefetch them.
if (options.urls) {
options.urls.forEach(prefetcher);
} else {
// If not, find all links and use IntersectionObserver.
Array.from(options.el.querySelectorAll(`a`), link => {
observer.observe(link);
// If the anchor matches a permitted origin
// ~> A `[]` or `true` means everything is allowed
if (!allowed.length || allowed.includes(link.hostname)) {
// If there are any filters, the link must not match any of them
isIgnored(link, ignores) || toPrefetch.add(link.href);
}
});
}
}, {timeout: options.timeout});
}
檢測link出現在視口
上面通過observer.observe(link)監視節點元素,其中observer是IntersectionObserver物件的例項,被監聽的節點物件出現在視口時,會執行new操作時傳入的回撥函式,並將出現在視口的節點物件通過陣列的形式傳給該回撥。然後在回撥中便利傳入的陣列,如果陣列中的元素包含在toPrefetch物件中,則取消對該元素的監視,並對該a標籤元素所對應的資源進行預載入。
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const link = entry.target;
if (toPrefetch.has(link.href)) {
observer.unobserve(link);
prefetcher(link.href);
}
}
});
});
非同步函式實現
如果瀏覽器支援requestIdleCallback,則使用原生的函式,如果不支援,則使用setTimeout函式做ployfill。
const requestIdleCallback = requestIdleCallback ||
function (cb) {
const start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
},
});
}, 1);
};
export default requestIdleCallback;
資源請求函式實現
預載入策略主要有三種
1.<link> prefetch
function linkPrefetchStrategy(url) {
return new Promise((resolve, reject) => {
const link = document.createElement(`link`);
link.rel = `prefetch`;
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
};
2.ajax載入
function xhrPrefetchStrategy(url) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.open(`GET`, url, req.withCredentials=true);
req.onload = () => {
(req.status === 200) ? resolve() : reject();
};
req.send();
});
}
3.Fetch請求載入
function highPriFetchStrategy(url) {
// TODO: Investigate using preload for high-priority
// fetches. May have to sniff file-extension to provide
// valid `as` values. In the future, we may be able to
// use Priority Hints here.
//
// As of 2018, fetch() is high-priority in Chrome
// and medium-priority in Safari.
return self.fetch == null
? xhrPrefetchStrategy(url)
: fetch(url, {credentials: `include`});
}
網路型別判斷
if (conn = navigator.connection) {
// Don`t prefetch if the user is on 2G. or if Save-Data is enabled..
if ((conn.effectiveType || ``).includes(`2g`) || conn.saveData) return;
}
將上面三種預載入方法封裝成函式,暴露給外部使用
const supportedPrefetchStrategy = support(`prefetch`)
? linkPrefetchStrategy
: xhrPrefetchStrategy;
function prefetcher(url, isPriority, conn) {
if (preFetched[url]) {
return;
}
if (conn = navigator.connection) {
// Don`t prefetch if the user is on 2G. or if Save-Data is enabled..
if ((conn.effectiveType || ``).includes(`2g`) || conn.saveData) return;
}
// Wanna do something on catch()?
return (isPriority ? highPriFetchStrategy : supportedPrefetchStrategy)(url).then(() => {
preFetched[url] = true;
});
};
export default prefetcher;