前端预加载技术分析
前端性能优化的关键无非就是两个"快"——资源下载更快,资源解析更快。前端的预加载技术给予了我们精准控制浏览器下载资源行为的能力,即可以让我们合理安排资源下载逻辑来完成性能优化。
浏览器渲染流程
在了解优化资源下载逻辑的手段之前,有必要先简单回顾一下不做任何优化时,浏览器自主解析HTML的逻辑。
- 浏览器下载HTML,至上而下构建DOM树
- 遇到JS标签或css标签则新起线程去下载资源,此过程阻塞DOM树构建
- JS资源下载完成后立即执行,阻塞DOM树和CSS规则树的构建
- CSS文件下载完成后构建CSS规则树,不阻塞DOM树构建,但是会阻塞JS的执行
- 根据DOM树和CSS规则树结合生成渲染树生成可视页面
可以看到要让网页快速渲染,关键点就在于如何减少可视页面首屏渲染前的必要JS和CSS下载和执行所引起的阻塞行为。为了达到这个目的有两种思路,一是减少必要资源下载量。二是减少必要资源下载时间。
减少必要资源下载量
defer
这里可能会让人觉得矛盾,既然是必要资源,那又如何减少下载量呢?关键在于可视页面渲染前,我们只要控制必要资源在首屏渲染后加载即可,这个属性用于改变浏览器处理外部脚本标签的行为,会在不阻塞文档解析的前提下载脚本,并将其延迟到首屏渲染之后,DOMContentLoaded事件触发之前执行。
另外一个比较相似的属性是async,但其作用只是单纯的异步下载脚本,下载完成后仍然会立即执行阻塞渲染,并且执行顺序会由于下载完成所需时间和标签顺序不一致原因也发生乱序,所以使用场景有限。
内联首屏CSS
由于CSS规则的构建不会阻塞DOM树构建,我们可以内联首屏CSS,减少下载外部样式的时间,配合Webpack能够轻松实现按需内联首屏CSS。
减少必要资源下载时间
DNS预解析
DNS请求虽然带宽要求小,但是延迟不可忽视。对于已知的某个资源URL可以通过如下形式在head标签中实现DNS预解析:
<link rel="dns-prefetch" href="//....">
Preconnect
同DNS预解析类似,我们也可以对将要连接的网站预先建立socket连接。该操作会在DNS解析之上进一步完成TCP握手和建立传输层协议。用法如下:
<link rel="preconnect" href="//....">
Preload
该属性专用于提前加载当前页面所需的资源,其关键在于解耦浏览器本身对于资源的获取与执行的强耦合。在HTML解析器解析到标签之前,preloader就会收集资源URL和资源类型并送给读取器开始有次序的获取资源。
加载优先级
由于浏览器对于每个域有并发请求数量限制,那么当请求过多时必然有个优先级。情况如下图:
preload的厉害之处就在于可以使用as属性强行改变资源加载优先级。当不存在as属性时等同于异步请求。
异步加载
还记得之前说过的async属性异步加载脚本吗?不实用之处就在于加载完成后会立即执行,而此刻可能仍未完成首屏渲染导致阻塞。
而preload的onload属性可以实现异步加载的同时,在onload事件之后再执行该脚本。对于一些与页面逻辑不强相关的js代码使用该方法加载优化性能有奇效,例如埋点统计等。
<link rel="preload" as="script" href="..." onload="let script = document.createElement('script');script.src = this.href;">
响应式加载
针对于不同的媒体设备,可能对某一资源的呈现要求也不一样。media属性可以做到根据设备加载相应资源。
<link rel="preload" as="image" href="..." media="(max-width: 720px)">
<link rel="preload" as="js" href="..." media="(min-width: 721px)">
PreFetch
与preload专注于当前页面不同,prefetch专注于将来要用到的资源,其加载优先级相当低,毕竟是当前页面不会用到的资源。
由于该属性并不局限于当前页,所以prefetch请求发起后,即使页面跳转仍不会中断,并且获取的资源无论是否可以缓存都会在网络堆栈中至少缓存5分钟。而preload的请求在页面跳转后会立刻中断。
-- EOF --