quicklink是一個js庫,能夠預加載出如今視口的網頁連接,提升用戶體驗。它的加載過程以下:
1.檢測網頁中的連接是否出如今視口中,等待連接出如今視口,執行步驟2。
2.等待瀏覽器空閒後執行3。
3.判斷當前的網絡鏈接是不是2G,若是是則中止執行,若是不是2G網絡,執行步驟4。
4.預加載連接指向資源。git
參考連接https://github.com/GoogleChro...github
quicklink的入口函數接受傳入的配置參數,經過Object.assign函數和默認的配置選項合併。接着執行timeoutFn異步方法,該方法接收一個回調函數,在回調中主要邏輯以下:
若是傳入的options參數中有urls屬性,則直接執行預加載,不然經過document.querySelectorAll方法獲取全部a標籤元素的NodeList,而後便利該元素節點列表,並監視該元素節點ajax
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}); }
上面經過observer.observe(link)監視節點元素,其中observer是IntersectionObserver對象的實例,被監聽的節點對象出如今視口時,會執行new操做時傳入的回調函數,並將出如今視口的節點對象經過數組的形式傳給該回調。而後在回調中便利傳入的數組,若是數組中的元素包含在toPrefetch對象中,則取消對該元素的監視,並對該a標籤元素所對應的資源進行預加載。app
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;
預加載策略主要有三種ide
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;