背景
八卦圖代表了古代算法數(shù)術(shù)的結(jié)晶,主要是為了提供一種預(yù)測(cè)人類行為的方法論,這種能提前預(yù)測(cè)的行為古往今來(lái)都備受人們追捧谦去,既然預(yù)測(cè)這個(gè)行為那么受歡迎,互聯(lián)網(wǎng)行業(yè)是不是也進(jìn)行了這方面的技術(shù)儲(chǔ)備呢蹦哼?沒(méi)錯(cuò)互聯(lián)網(wǎng)行業(yè)也已經(jīng)存在此功能了鳄哭。
自從互聯(lián)網(wǎng)興起之后,網(wǎng)站也越來(lái)越漂亮纲熏,越來(lái)越大妆丘,性能就是大家一致追求的方向,大家也都想預(yù)測(cè)用戶下一步行為局劲。從瀏覽器廠商到各個(gè)開(kāi)發(fā)者都想通過(guò)各種方式來(lái)提升用戶的體驗(yàn)勺拣,所以提前知道用戶的下一步行為,預(yù)先加載用戶下一個(gè)頁(yè)面的資源就是開(kāi)發(fā)者要追求的下一個(gè)技術(shù)方向鱼填。之前大家都比較習(xí)慣使用 JS 來(lái)動(dòng)態(tài)控制頁(yè)面內(nèi)容药有,殊不知我們還有一些更好的方案來(lái)替換這些方法。
我們這么搞苹丸?
- 簡(jiǎn)單粗暴的可以直接頁(yè)簽最下端引用加載愤惰;
- 高級(jí)一點(diǎn):我可以在執(zhí)行的 JS 代碼來(lái)進(jìn)行 load 加載;
更高級(jí)的替換方案是什么呢赘理?
聽(tīng)小道消息說(shuō)谷歌這么搞宦言!
當(dāng)你在谷歌瀏覽器輸入域名一部分時(shí)會(huì)根據(jù)記錄提示補(bǔ)全,如下圖所示:
你知道嗎商模?這個(gè)時(shí)候谷歌已經(jīng)對(duì)你有意向訪問(wèn)的域名搞事情了奠旺。
因?yàn)榇穗A段蜘澜,實(shí)際上谷歌已經(jīng)大概知道你即將訪問(wèn)哪個(gè)頁(yè)面,此時(shí)瀏覽器會(huì)根據(jù)捕獲的頁(yè)面 URL 進(jìn)行提前操作响疚,如提前解析 DNS兼都,提前加載部分資源等事。具體能預(yù)先處理到何種程度稽寒,這個(gè)我還真的沒(méi)有實(shí)際監(jiān)控到太確切的數(shù)據(jù),把它當(dāng)成小道消息吧趟章。
這種預(yù)先處理頁(yè)面的方式杏糙,不只是開(kāi)發(fā)的程序猿與攻城獅們,瀏覽器廠商也在想辦法去推動(dòng)這個(gè)行為的”合法化“蚓土,也就是進(jìn)入 W3C 的標(biāo)準(zhǔn)宏侍,當(dāng)然路途坎坷,但我們還是見(jiàn)到了一些效果蜀漆,這個(gè)效果就是我們接下來(lái)要講的<span style="color: #f00; font-size: 16px;">Resource Hints</span>谅河。
Resource Hints 是什么?
Resource Hints 實(shí)際上就是“合法化”的提供了使用瀏覽器原始語(yǔ)言來(lái)進(jìn)行的一些提前預(yù)測(cè)行為的能力确丢,W3C 也陸續(xù)增加了很多功能绷耍,主要包含如下:
- dns-prefetch
- preconnect
- prefetch
- prerender
- preload
根據(jù) W3C 的記錄,最早的草案是在2014年10月21日提出的鲜侥,中途經(jīng)過(guò)了幾十個(gè)版本的迭代(好吧褂始,我的確是一個(gè)版本一個(gè)版本的翻來(lái)看的),最新的草案是2018年1月15日發(fā)布的描函。W3C 也針對(duì)此進(jìn)行了多次的優(yōu)化崎苗,雖然現(xiàn)在的使用頻率以及兼容性依然還不是特別的完美。
Resource Hints能做什么舀寓?
談了這么多胆数,那 Resource Hints 具體能干什么呢?我們來(lái)一個(gè)一個(gè)的說(shuō)。
1、dns-prefetch
談到這里雀哨,不得不先介紹一下 DNS 是一個(gè)什么東西瞧预。
DNS(Domain Name System) - 域名和 IP 地址相互映射的一個(gè)分部式數(shù)據(jù)庫(kù)。
當(dāng)我們?cè)L問(wèn)一個(gè)域名的時(shí)候儿礼,DNS 系統(tǒng)會(huì)根據(jù)域名去一層一層的開(kāi)始解析,從全世界的分布系統(tǒng)中查詢對(duì)應(yīng)的 IP 地址,然后通過(guò) IP 地址來(lái)訪問(wèn)對(duì)應(yīng)的網(wǎng)頁(yè)數(shù)據(jù)骂租。
DNS 解析時(shí)間會(huì)導(dǎo)致大量用戶感知延遲。DNS 解析所花費(fèi)的時(shí)間是高度可變的斑司。延遲時(shí)間范圍從大約 1ms (本地緩存的結(jié)果)到通常幾秒鐘的時(shí)間渗饮,所以為了解決此延遲但汞,W3C 提供了 dns-prefetch 功能,兼容性如圖:
主流瀏覽器兼容:
- IE10+互站;
- Firefox 3.0+私蕾;
- Chrome All;
- Safari 5.0+胡桃;
- Opera 15.0+踩叭;
我們可以在首頁(yè)添加如下語(yǔ)句來(lái)使用:
<link rel="dns-prefetch" >
當(dāng)存在 DNS 預(yù)解析時(shí),本頁(yè)面無(wú)論是否存在此域名下的資源翠胰,都會(huì)對(duì)此域名進(jìn)行 DNS 解析容贝,當(dāng)實(shí)際用到時(shí),瀏覽器就無(wú)需再去解析之景,我們來(lái)看一下實(shí)際使用之后再次進(jìn)入頁(yè)面的加載情況斤富,
最直觀的效果,在頁(yè)面加載中少了 DNS Lookup 的時(shí)間锻狗,也就是解析 DNS 的時(shí)間满力。
DNS 預(yù)解析主要是用戶訪問(wèn)鏈接之前解析域名的一種嘗試,這是使用計(jì)算機(jī)正常的 DNS 解析機(jī)制完成的轻纪,一旦解析了域名油额,如果用戶導(dǎo)航到該域名,就不會(huì)因?yàn)?DNS 解析時(shí)間而出現(xiàn)延遲刻帚,此策略比較適用的場(chǎng)景是悔耘,網(wǎng)頁(yè)中會(huì)引用多域名下的信息,我們可以針對(duì)使用到的所有域名進(jìn)行解析我擂,所有這些工作都在用戶讀取頁(yè)面時(shí)使用最少的 CPU 和網(wǎng)絡(luò)資源來(lái)完成衬以,當(dāng)用戶訪問(wèn)這些預(yù)先解析的網(wǎng)站時(shí),平均會(huì)節(jié)省 1-20ms 的時(shí)間校摩,更重要的是看峻,如果用戶遇到了 DNS 解析的“最壞情況”延遲,那對(duì)于網(wǎng)絡(luò)延遲的提升是很顯著的衙吩。
dns-preferch應(yīng)用
我廠項(xiàng)目中均會(huì)使用 CDN 以及圖片服務(wù)器互妓,并且圖片服務(wù)器也不只一臺(tái),僅僅這些內(nèi)容坤塞,就會(huì)造成多次的 DNS 解析冯勉,針對(duì)此情況我們使用 dns-prefetch,對(duì)使用到的域名進(jìn)行預(yù)解析摹芙,減少后續(xù)域名 DNS 解析時(shí)間灼狰,以此來(lái)追求更進(jìn)一步的前端性能。
項(xiàng)目使用的 CDN 以及圖片服務(wù)器地址的 DNS 預(yù)解析浮禾,我們均已經(jīng)集成進(jìn)我們內(nèi)部 Vue 單頁(yè)面腳手架 Gaea[8]交胚。
2份汗、preconnect
當(dāng)我們調(diào)用請(qǐng)求時(shí),會(huì)經(jīng)歷怎樣的步驟呢蝴簇?
針對(duì)實(shí)際的 http 請(qǐng)求杯活,DNS 解析一步,在上一個(gè)環(huán)節(jié)已經(jīng)提前處理熬词,那么后續(xù)的步驟我們可以進(jìn)行提前處理嗎旁钧?
preconnect 功能就是為此而來(lái)的。
- 針對(duì) HTTP :可以提前為 URL 建立早期鏈接互拾,可提前完成包含 DNS 解析 + TCP 三次握手環(huán)節(jié)均践;
- 針對(duì) HTTPS :可以提前為 URL 建立早期鏈接,可提前完成包括 DNS 解析 + TCP 三次握手 + TLS / SSL 握手環(huán)節(jié)摩幔;
再來(lái)看看其兼容性如何:
主流瀏覽器兼容:
- IE15+ (http 時(shí)才支持);
- Firefox 39.0+鞭铆;
- Chrome 46.0+或衡;
- Safari 11.1+;
- Opera 33.0+车遂;
使用的代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Hints Demo</title>
<link rel="preconnect" >
</head>
<body>
<a ></a>
</body>
</html>
預(yù)鏈接功能與存在此鏈接的頁(yè)面放在一起封断。打開(kāi)此頁(yè)面會(huì)直接進(jìn)行預(yù)鏈接嘗試,當(dāng)點(diǎn)擊了跳轉(zhuǎn)時(shí)舶担,URL 則可直接讀取數(shù)據(jù)坡疼。
如果你期望預(yù)鏈接的地址與當(dāng)前網(wǎng)站不在同域,我們可以采用跨域方式來(lái)進(jìn)行預(yù)鏈接衣陶。
<link rel="preconnect" crossorigin>
實(shí)際添加之后與添加前柄瑰,后續(xù)頁(yè)面的頁(yè)面請(qǐng)求情況對(duì)比:
具體能節(jié)省多長(zhǎng)時(shí)間這個(gè)會(huì)根據(jù)網(wǎng)絡(luò)延遲來(lái)確定,上圖只是做了一個(gè)加載順序的表示剪况,并且教沾,隨著網(wǎng)速的越來(lái)越快,這個(gè)差值的確也會(huì)越來(lái)越小译断。
preconnect應(yīng)用
我們開(kāi)發(fā)的項(xiàng)目中會(huì)存在很多引導(dǎo)性的鏈接授翻,可以幫助用戶訪問(wèn)到自己想去的網(wǎng)站,此頁(yè)面就會(huì)存在很多不同域名下的鏈接孙咪,因此堪唐,我們采用了 preconnect 的方式來(lái)對(duì)這些數(shù)據(jù)的 http 鏈接進(jìn)行的提前請(qǐng)求,當(dāng)訪問(wèn)到對(duì)應(yīng)頁(yè)面時(shí)減少了頁(yè)面鏈接的時(shí)間翎蹈。
3淮菠、prefetch
既然一個(gè)內(nèi)容除了獲取數(shù)據(jù)之外都能提前進(jìn)行,那何不直接提前加載一個(gè)文件呢荤堪?prefetch 你值得擁有兜材,兼容性如下圖:
主流瀏覽器兼容:
- IE11+理澎;
- Firefox 2.0+;
- Chrome 8.0+曙寡;
- Safari 全部不支持糠爬;
- Opera 15.0+;
prefetch 是一個(gè)低優(yōu)先級(jí)的資源加載策略举庶,此策略允許瀏覽器在后臺(tái)(空閑時(shí)間)獲取稍后可能需要的資源执隧,并將它們存儲(chǔ)在瀏覽器緩存中。一旦頁(yè)面完成加載户侥,就會(huì)開(kāi)始下載額外資源镀琉,如果用戶點(diǎn)擊了預(yù)先獲取的鏈接,則會(huì)立即加載內(nèi)容蕊唐。
如果在執(zhí)行預(yù)加載策略時(shí)屋摔,中途出現(xiàn)了高優(yōu)先級(jí)的請(qǐng)求,則預(yù)加載的線程占用會(huì)立即釋放替梨,并且會(huì)將已經(jīng)加載的全部刪除钓试,已讓出線程來(lái)執(zhí)行當(dāng)前頁(yè)面的內(nèi)容。
來(lái)看看代碼的引入方式:
HTML: <link rel="prefetch" href="/uploads/images/pic.png">
HTTP Header: Link: </uploads/images/pic.png>; rel=prefetch
預(yù)加載類型我們可以通過(guò) as 來(lái)設(shè)置, as 屬性為可選屬性副瀑,其可以與資源上下文通信弓熏,這樣用戶可以優(yōu)化預(yù)加載的抓取過(guò)程。
<link rel="prefetch" href="/uploads/images/pic.png" as="image">
HTTP Header: Link: </uploads/images/pic.png>; rel=prefetch; as=image
as包含的類型如下:
引用情況 預(yù)加載類型設(shè)置
<audio> <link rel=preload as=audio href=...>
<video> <link rel=preload as=video href=...>
<track> <link rel=preload as=track href=...>
<script>, Worker's importScripts <link rel=preload as=script href=...>
<link rel=stylesheet>, CSS @import <link rel=preload as=style href=...>
CSS @font-face <link rel=preload as=font href=...>
<img>, <picture>, srcset, imageset <link rel=preload as=image href=...>
SVG's <image>, CSS *-image <link rel=preload as=image href=...>
XHR, fetch <link rel=preload as=fetch crossorigin href=...>
Worker, SharedWorker <link rel=preload as=worker href=...>
<embed> <link rel=preload as=embed href=...>
<object> <link rel=preload as=object href=...>
<iframe>, <frame> <link rel=preload as=document href=...>
如果我們預(yù)加載的資源與執(zhí)行代碼頁(yè)面存在跨域的話糠睡,我們需要配置跨域挽鞠,crossorigin 為一個(gè)配置參數(shù):
<link rel="prefetch" as="html" crossorigin="use-credentials">
CORS 配置,詳細(xì)可參考CORS 配置:
首頁(yè)我們預(yù)加載了一個(gè) base.js :
進(jìn)入二級(jí)頁(yè)面之后狈孔,base.js 直接從緩存讀刃湃稀:
prefetch應(yīng)用
開(kāi)發(fā)項(xiàng)目中,不排除會(huì)遇到管理端類型的系統(tǒng)均抽,此類系統(tǒng)涉及到一些表格類組件以及報(bào)表類組件狮杨,重復(fù)使用如分頁(yè),表格到忽,彈窗等多個(gè)組件橄教。為避免重復(fù)加載影響性能,我們采用 prefetch 對(duì)可能會(huì)造成重復(fù)加載的資源進(jìn)行提前預(yù)加載喘漏,以保證我們可以更快速的獲取組件資源渲染頁(yè)面护蝶,并且在部分頁(yè)面我們還會(huì)針對(duì)性的對(duì)下一個(gè)頁(yè)面的業(yè)務(wù) JS 進(jìn)行動(dòng)態(tài)預(yù)加載,引用方式如下圖:
4翩迈、prerender
我們已經(jīng)從各方面都提前處理了持灰,什么?還不知足负饲,那咱們來(lái)個(gè)重量級(jí)的堤魁。
我們直接把接下來(lái)可能訪問(wèn)的頁(yè)面直接提前渲染了如何喂链?
prerender 會(huì)收集用戶接下來(lái)要訪問(wèn)的頁(yè)面,并且在瀏覽器中創(chuàng)建一個(gè)隱藏 Tab妥泉,并且在隱藏 Tab 中提前加載頁(yè)面椭微。當(dāng)跳轉(zhuǎn)到此頁(yè)面時(shí),相當(dāng)于直接切換到隱藏 Tab盲链。
來(lái)看看功能強(qiáng)大的 prerender 兼容性如何:
[站外圖片上傳中...(image-eace8-1562052143748)]
主流瀏覽器兼容:
- IE11蝇率,高版本也不支持;
- Firefox 全部不支持刽沾;
- Chrome 13.0+本慕;
- Safari 全部不支持;
- Opera 15.0+侧漓;
使用方式如 prefetch 一樣锅尘,依舊是使用link標(biāo)簽即可:
<link rel="prerender" >
整個(gè)prerender運(yùn)行流程如下圖:
[站外圖片上傳中...(image-d39f9a-1562052143748)]
創(chuàng)建 prerender 頁(yè)面過(guò)程:
當(dāng)訪問(wèn)頁(yè)面遇到 <link rel=”prerender”> 資源標(biāo)簽時(shí),會(huì)啟動(dòng) prerender , ResourceDispatcherHost 接收到一個(gè) resource 類型為 ::Prerender 的資源請(qǐng)求--但是這個(gè)請(qǐng)求永遠(yuǎn)不會(huì)以網(wǎng)絡(luò)請(qǐng)求發(fā)送布蔗,相反它會(huì)用來(lái)創(chuàng)建 PrerenderContents 的請(qǐng)求信號(hào)藤违,并且取消請(qǐng)求本身。PrerenderContents 存儲(chǔ)于 PrerenderManager 中何鸡, PrerenderManager 中會(huì)統(tǒng)一管理 PrerenderContents 目錄,允許保留最近創(chuàng)建的少量 PrerenderContents牛欢,當(dāng)前的 PrerenderContents 保留一個(gè)頁(yè)面最多只能 30ms骡男,頁(yè)面保留時(shí)間的具體時(shí)長(zhǎng)是谷歌瀏覽器默認(rèn)的,此值可能會(huì)受到谷歌瀏覽器更新影響傍睹,如果容量達(dá)到存儲(chǔ)上線隔盛,系統(tǒng)會(huì)將舊頁(yè)面刪除回收。
雖然此功能只是谷歌更新的一個(gè)小功能拾稳,但是因?yàn)橐恍﹩?wèn)題使得此功能變得復(fù)雜化:
- 最小化資源征用吮炕;
- 處理動(dòng)態(tài)媒體[視頻、音頻访得、插件龙亲、畫(huà)布];
- 取消某些情況下的頁(yè)面悍抑;
- 最小化服務(wù)器的副作用鳄炉;
- 共享本地存儲(chǔ)[cookie、sessionStorage等]搜骡;
注意:預(yù)渲染雖然美好拂盯,但是會(huì)造成比較大的流量以及性能的損耗,所以瀏覽器默認(rèn)關(guān)閉支持此功能记靡。
如果我們需要使用谈竿,可以通過(guò)以下步驟開(kāi)啟:
- 打開(kāi)高級(jí)設(shè)置团驱;
- 開(kāi)啟如圖功能即可;
[站外圖片上傳中...(image-d2696c-1562052143748)]
在預(yù)渲染的時(shí)候空凸,瀏覽器的開(kāi)發(fā)者工具是無(wú)法監(jiān)控到具體流程的嚎花。如果需要監(jiān)控,我們需要訪問(wèn)瀏覽器的:chrome://net-internals/#prerender 功能劫恒。
5贩幻、preload
其實(shí)瀏覽器能提前幫忙做的事情已經(jīng)做的差不多了,不過(guò) W3C 不滿足两嘴,又提供了一個(gè) preload丛楚,preload 這又是什么呢?
preload 也是加載資源的功能憔辫,但區(qū)別于 prefetch 的是趣些,prefetch 的目的在于針對(duì)即將訪問(wèn)的頁(yè)面使用低優(yōu)先級(jí)來(lái)提前預(yù)加載資源,而preload則會(huì)針對(duì)當(dāng)前頁(yè)面使用高優(yōu)先級(jí)來(lái)加載資源贰您,如字體文件坏平,圖片等,但值得注意的是锦亦,它不會(huì)阻塞 onload 事件的執(zhí)行舶替。
preload兼容性如下圖:
主流瀏覽器兼容:
- IE17+(僅Http支持);
- Firefox 全部不支持杠园;
- Chrome 50.0+顾瞪;
- Safari 11.1+;
- Opera 37.0+抛蚁;
引用的方式與 prefetch 一致陈醒,甚至 as 以及 corssorigin 配置也一致,可參考上文:
<link rel=“preload” href=“./js/base.js” as=“script”>
當(dāng)頁(yè)面執(zhí)行 preload 加載文件時(shí)瞧甩,不會(huì)阻礙其他文件的加載:
如果系統(tǒng)中存在字體的話钉跷,會(huì)出現(xiàn)字體閃動(dòng)的問(wèn)題,因字體資源普遍都比較大肚逸,所以此時(shí)我們可以使用 preload 來(lái)預(yù)加載字體爷辙,這樣可以很好的解決頁(yè)面閃爍的問(wèn)題。
因?yàn)樘崆熬彺嫖募?huì)對(duì)用戶的流量造成浪費(fèi)朦促,所以瀏覽器在預(yù)加載文件后的 3s 內(nèi)沒(méi)有使用的此文件會(huì)提示警告錯(cuò)誤犬钢。
但很多情況下我們可能需要?jiǎng)討B(tài)來(lái)添加預(yù)加載的內(nèi)容,我們可以使用如下方式來(lái)動(dòng)態(tài)添加:
preload: function (srcList) {
for (var i = 0; i < srcList.length; i++) {
var resource = srcList[i].replace(/http:|https:/, '');
var head = document.getElementsByTagName('head')[0];
var rsTag = document.createElement('link');
rsTag.setAttribute('rel', 'prefetch');
rsTag.setAttribute('href', resource);
head.appendChild(rsTag);
}
},
因?yàn)楣δ艿男再|(zhì)不同思灰,所以也不是所有內(nèi)容都可以動(dòng)態(tài)添加的玷犹,其實(shí)可以動(dòng)態(tài)添加的主要包括: prefetch 、 preconnect 以及 prerender。
preload應(yīng)用
提前預(yù)加載比較適合一些大文件資源歹颓,單頁(yè)面應(yīng)用在開(kāi)發(fā)時(shí)因通過(guò)構(gòu)建工具進(jìn)行了部分合并坯屿,導(dǎo)致資源文件并不會(huì)太多,針對(duì)此種情況我們通過(guò)使用 preload 對(duì)這些資源文件進(jìn)行提高優(yōu)先級(jí)的預(yù)加載巍扛,讓整體頁(yè)面加載速度更快领跛。
結(jié)語(yǔ)
Resource Hints 包含的功能大概讓大家了解了一下,當(dāng)然大家有人可能已經(jīng)用到了撤奸,也可能未用到吠昭,但不可否認(rèn),在國(guó)內(nèi)的瀏覽器大環(huán)境下的確兼容性不夠好胧瓜,國(guó)外的大多數(shù)網(wǎng)站都已經(jīng)采用了各種方式的預(yù)加載策略矢棚,所以前端大大們,如果沒(méi)有使用過(guò)的話府喳,也可以嘗試使用一下蒲肋,因?yàn)槿绻患嫒菀膊粫?huì)出現(xiàn)問(wèn)題,何樂(lè)而不為呢钝满?
參考文獻(xiàn):
[1] W3C 官方文檔:https://www.w3.org/TR/resource-hints/
[2] DNS 原理:http://blog.51cto.com/369369/812889
[3] CORS 配置:https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
[4] Resource Hints:https://www.keycdn.com/blog/resource-hints/
[5] Prerender: http://www.chromium.org/developers/design-documents/prerender
[6] Preload:What Is It Good For兜粘?:https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/
[7] Understanding prefetching and how Facebook uses prefetching:https://www.facebook.com/business/help/1514372351922333
[8] Gaea 參考文檔:[https://www.npmjs.com/package/gaea-cli](https://www.npmjs.com/package/gaea-cli)