背景
八卦图代表了古代算法数术的结晶,主要是为了提供一种预测人类行为的方法论,这种能提前预测的行为古往今来都备受人们追捧,既然预测这个行为那么受欢迎,互联网行业是不是也进行了这方面的技术储备呢?没错互联网行业也已经存在此功能了。
自从互联网兴起之后,网站也越来越漂亮,越来越大,性能就是大家一致追求的方向,大家也都想预测用户下一步行为。从浏览器厂商到各个开发者都想通过各种方式来提升用户的体验,所以提前知道用户的下一步行为,预先加载用户下一个页面的资源就是开发者要追求的下一个技术方向。之前大家都比较习惯使用 JS 来动态控制页面内容,殊不知我们还有一些更好的方案来替换这些方法。
我们这么搞?
- 简单粗暴的可以直接页签最下端引用加载;
- 高级一点:我可以在执行的 JS 代码来进行 load 加载;
更高级的替换方案是什么呢?
听小道消息说谷歌这么搞!
当你在谷歌浏览器输入域名一部分时会根据记录提示补全,如下图所示:
你知道吗?这个时候谷歌已经对你有意向访问的域名搞事情了。
因为此阶段,实际上谷歌已经大概知道你即将访问哪个页面,此时浏览器会根据捕获的页面 URL 进行提前操作,如提前解析 DNS,提前加载部分资源等事。具体能预先处理到何种程度,这个我还真的没有实际监控到太确切的数据,把它当成小道消息吧。
这种预先处理页面的方式,不只是开发的程序猿与攻城狮们,浏览器厂商也在想办法去推动这个行为的”合法化“,也就是进入 W3C 的标准,当然路途坎坷,但我们还是见到了一些效果,这个效果就是我们接下来要讲的Resource Hints。
Resource Hints 是什么?
Resource Hints 实际上就是“合法化”的提供了使用浏览器原始语言来进行的一些提前预测行为的能力,W3C 也陆续增加了很多功能,主要包含如下:
- dns-prefetch
- preconnect
- prefetch
- prerender
- preload
根据 W3C 的记录,最早的草案是在2014年10月21日提出的,中途经过了几十个版本的迭代(好吧,我的确是一个版本一个版本的翻来看的),最新的草案是2018年1月15日发布的。W3C 也针对此进行了多次的优化,虽然现在的使用频率以及兼容性依然还不是特别的完美。
Resource Hints能做什么?
谈了这么多,那 Resource Hints 具体能干什么呢?我们来一个一个的说。
1、dns-prefetch
谈到这里,不得不先介绍一下 DNS 是一个什么东西。
DNS(Domain Name System) - 域名和 IP 地址相互映射的一个分部式数据库。
当我们访问一个域名的时候,DNS 系统会根据域名去一层一层的开始解析,从全世界的分布系统中查询对应的 IP 地址,然后通过 IP 地址来访问对应的网页数据。
DNS 解析时间会导致大量用户感知延迟。DNS 解析所花费的时间是高度可变的。延迟时间范围从大约 1ms (本地缓存的结果)到通常几秒钟的时间,所以为了解决此延迟,W3C 提供了 dns-prefetch 功能,兼容性如图:
主流浏览器兼容:
- IE10+;
- Firefox 3.0+;
- Chrome All;
- Safari 5.0+;
- Opera 15.0+;
我们可以在首页添加如下语句来使用:
当存在 DNS 预解析时,本页面无论是否存在此域名下的资源,都会对此域名进行 DNS 解析,当实际用到时,浏览器就无需再去解析,我们来看一下实际使用之后再次进入页面的加载情况,
最直观的效果,在页面加载中少了 DNS Lookup 的时间,也就是解析 DNS 的时间。
DNS 预解析主要是用户访问链接之前解析域名的一种尝试,这是使用计算机正常的 DNS 解析机制完成的,一旦解析了域名,如果用户导航到该域名,就不会因为 DNS 解析时间而出现延迟,此策略比较适用的场景是,网页中会引用多域名下的信息,我们可以针对使用到的所有域名进行解析,所有这些工作都在用户读取页面时使用最少的 CPU 和网络资源来完成,当用户访问这些预先解析的网站时,平均会节省 1-20ms 的时间,更重要的是,如果用户遇到了 DNS 解析的“最坏情况”延迟,那对于网络延迟的提升是很显著的。
dns-preferch应用
我厂项目中均会使用 CDN 以及图片服务器,并且图片服务器也不只一台,仅仅这些内容,就会造成多次的 DNS 解析,针对此情况我们使用 dns-prefetch,对使用到的域名进行预解析,减少后续域名 DNS 解析时间,以此来追求更进一步的前端性能。
项目使用的 CDN 以及图片服务器地址的 DNS 预解析,我们均已经集成进我们内部 Vue 单页面脚手架 Gaea[8]。
2、preconnect
当我们调用请求时,会经历怎样的步骤呢?
针对实际的 http 请求,DNS 解析一步,在上一个环节已经提前处理,那么后续的步骤我们可以进行提前处理吗?
preconnect 功能就是为此而来的。
- 针对 HTTP :可以提前为 URL 建立早期链接,可提前完成包含 DNS 解析 + TCP 三次握手环节;
- 针对 HTTPS :可以提前为 URL 建立早期链接,可提前完成包括 DNS 解析 + TCP 三次握手 + TLS / SSL 握手环节;
再来看看其兼容性如何:
主流浏览器兼容:
- IE15+ (http 时才支持);
- Firefox 39.0+;
- Chrome 46.0+;
- Safari 11.1+;
- Opera 33.0+;
使用的代码如下:
Resource Hints Demo
预链接功能与存在此链接的页面放在一起。打开此页面会直接进行预链接尝试,当点击了跳转时,URL 则可直接读取数据。
如果你期望预链接的地址与当前网站不在同域,我们可以采用跨域方式来进行预链接。
实际添加之后与添加前,后续页面的页面请求情况对比:
具体能节省多长时间这个会根据网络延迟来确定,上图只是做了一个加载顺序的表示,并且,随着网速的越来越快,这个差值的确也会越来越小。
preconnect应用
我们开发的项目中会存在很多引导性的链接,可以帮助用户访问到自己想去的网站,此页面就会存在很多不同域名下的链接,因此,我们采用了 preconnect 的方式来对这些数据的 http 链接进行的提前请求,当访问到对应页面时减少了页面链接的时间。
3、prefetch
既然一个内容除了获取数据之外都能提前进行,那何不直接提前加载一个文件呢?prefetch 你值得拥有,兼容性如下图:
主流浏览器兼容:
- IE11+;
- Firefox 2.0+;
- Chrome 8.0+;
- Safari 全部不支持;
- Opera 15.0+;
prefetch 是一个低优先级的资源加载策略,此策略允许浏览器在后台(空闲时间)获取稍后可能需要的资源,并将它们存储在浏览器缓存中。一旦页面完成加载,就会开始下载额外资源,如果用户点击了预先获取的链接,则会立即加载内容。
如果在执行预加载策略时,中途出现了高优先级的请求,则预加载的线程占用会立即释放,并且会将已经加载的全部删除,已让出线程来执行当前页面的内容。
来看看代码的引入方式:
HTML:
HTTP Header: Link: ; rel=prefetch
预加载类型我们可以通过 as 来设置, as 属性为可选属性,其可以与资源上下文通信,这样用户可以优化预加载的抓取过程。
HTTP Header: Link: ; rel=prefetch; as=image
as包含的类型如下:
引用情况 预加载类型设置