浅析Preload/Prefetch性能优化
在网络请求中,我们在使用到某些资源比如:图片,JS,CSS 等等,在执行之前总需要等待资源的下载,如果我们能做到预先加载资源,那在资源执行的时候就不必等待网络的开销,这时候就轮到 preload 大显身手的时候了。
一、preload 提前加载
preload 是一个新的 Web 标准,在页面生命周期中提前加载你指定的资源,同时确保在浏览器的主要渲染机制启动之前。
这样就可以保证了其不会阻止浏览器的渲染并提前加载资源以此来提高性能。通常使用 preload 是用来加载图片、CSS、JavaScript和字体文件。
1、语法:
preload 的语法和加载 CSS 类似:
<link rel="stylesheet" href="styles/main.css"> // 加载CSS
除了要把 rel 改为 preload ,同时还要指定 as 属性,下面是一个简单的例子
<head>
<meta charset="utf-8">
<title>JS and CSS preload example</title>
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>bouncing balls</h1>
<canvas></canvas>
<script src="main.js" defer></script>
</body>
使用 as 来指定资源的类型可以使浏览器:
(1)更加精确地确定资源加载的优先级。
(2)将其存储在缓存中以备将来使用,并在适当时重用资源。
(3)将正确的内容安全策略应用于资源。
(4)为其设置正确的 Accept 请求标头。
preload 支持如下资源的加载:audio、document、embed、fetch、font、image、object、script、style、track、worker、video(浏览器暂未实现)
若你加载了一个 CORS 开启的资源,必须要加上 crossorigin 属性:
<link rel="preload" href="https://example.com/fonts/font.woff" as="font" crossorigin>
除了使用 html 标签来使用 preload,还可以使用如下方式:
<script>
var res = document.createElement("link");
res.rel = "preload";
res.as = "style";
res.href = "styles/other.css";
document.head.appendChild(res);
</script>
当浏览器解析到这行代码就会去加载 href 中对应的资源但不执行,待到真正使用到的时候再执行,
另一种方式方式就是在 HTTP 响应头中加上 preload 字段:
Link: <https://example.com/other/styles.css>; rel=preload; as=style
这种方式比通过 Link 方式加载资源方式更快,请求在返回还没到解析页面的时候就已经开始预加载资源了。
2、preload 的浏览器支持情况如下:
3、注意:
(1)浏览器使用 preload 加载资源的时候并没有执行里面的代码。比如 :
//myscript.js
alert(1)
<link rel="preload" href="myscript.js" as="script">
当使用浏览器加载时,并不会有弹窗。
(2)若下载时发生页面导航,preload 进行中的资源会被取消。
二、prefetch 预判加载
prefetch 是提示浏览器,用户在下次导航时可能会使用的资源,因此浏览器为了提升性能可以提前加载、缓存资源。prefetch 的加载优先级相对较低,浏览器在空闲的时候才会在后台加载。
prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源。
它的用法跟 preload 是一样的,也是3种:
// 1、link
<link rel="prefetch" href="/myscript.js" as="script">
// 2、JS
<script>
var res = document.createElement("link");
res.rel = "prefetch";
res.as = "style";
res.href = "styles/other.css";
document.head.appendChild(res);
</script>
// 3、http header头模式
Link: <https://example.com/other/styles.css>; rel=prefetch; as=style
相对于 preload 而 prefetch 应用在一个不同的场景:跨导航、跨页面使用资源,例如页面 A 初始化了一个页面 B 中关键资源的 prefetch 请求,则关键资源的加载和页面导航可以并行执行,但若是 preload 它会在页面 A 的unload 事件中立即取消。
注意:
(1)prefetch 优先级较低,所以加载的资源有可能会被取消,例如:在网络很差的时候,prefetch 一个大字体文件的时候就有可能被取消。
三、preload与prefetch的区别
1、当一个资源被 preload 或者 prefetch 获取后,它将被放在内存缓存中等待被使用,如果资源未存在有效的缓存机制(如 cache-control 或 max-age),它将被存储在 HTTP 缓存中可以被不同页面所使用。
正确使用 preload/prefetch 不会造成二次下载,也就说:当页面上使用到这个资源时候 preload 资源还没下载完,这时候不会造成二次下载,会等待第一次下载并执行脚本。
对于 preload 来说,一旦页面关闭了,它就会立即停止 preload 获取资源,而对于 prefetch 资源,即使页面关闭,prefetch 发起的请求仍会进行不会中断。
2、什么情况会导致二次获取?
不要将 preload 和 prefetch 进行混用,它们分别适用于不同的场景,对于同一个资源同时使用 preload 和 prefetch 会造成二次的下载。
preload 字体不带 crossorigin 也将会二次获取! 确保你对 preload 的字体添加 crossorigin 属性,否则他会被下载两次,这个请求使用匿名的跨域模式。这个建议也适用于字体文件在相同域名下,也适用于其他域名的获取(比如说默认的异步获取)。
preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源,而 prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。所以建议:对于当前页面很有必要的资源使用 preload,对于可能在将来的页面中使用的资源使用 prefetch。
3、这将会浪费用户的带宽吗?
用 “preload” 和 “prefetch” 情况下,如果资源不能被缓存,那么都有可能浪费一部分带宽,在移动端请慎用。
没有用到的 preload 资源在 Chrome 的 console 里会在 onload 事件 3s 后发生警告。
The resource http://localhost:8082/js/index.js was preloaded using link preload but not used
within a few seconds from the window’s load event.
Please make sure it has an appropriate as value and it is preloaded intentionally.
原因是你可能为了改善性能使用 preload 来缓存一定的资源,但是如果没有用到,你就做了无用功。在手机上,这相当于浪费了用户的流量,所以明确你要 preload 对象。
4、不同资源浏览器优先级
一个资源的加载的优先级被分为五个级别,分别是:
- Highest 最高
- High 高
- Medium 中等
- Low 低
- Lowest 最低
(1)HTML/CSS 资源,其优先级是最高的
(2)font 字体资源,优先级分别为 Highest/High
(3)图片资源,如果出现在视口中,则优先级为 High,否则为 Low
(4)而 script 脚本资源就比较特殊,优先级不一,脚本根据它们在文件中的位置是否异步、延迟或阻塞获得不同的优先级:
- 网络在第一个图片资源之前阻塞的脚本在网络优先级中是 High
- 网络在第一个图片资源之后阻塞的脚本在网络优先级中是 Medium
- 异步/延迟/插入的脚本(无论在什么位置)在网络优先级中是 Low
对于使用 prefetch 获取资源,其优先级默认为最低,Lowest,可以认为当浏览器空闲的时候才会去获取的资源。
而对于 preload 获取资源,可以通过 "as" 或者 "type" 属性来标识他们请求资源的优先级(比如说 preload 使用 as="style" 属性将获得最高的优先级,即使资源不是样式文件)
没有 “as” 属性的将被看作异步请求。
5、与 async/defer 加载方式对比
使用 async/defer 属性在加载脚本的时候不阻塞 HTML 的解析,defer 加载脚本执行会在所有元素解析完成,DOMContentLoaded 事件触发之前完成执行。它的用途其实跟 preload 十分相似。你可以使用 defer 加载脚本在 head 末尾,这比将脚本放在 body 底部效果来的更好。
(1)它相比于 preload 加载的优势在于浏览器兼容性好,从 caniuse 上看基本上所有浏览器都支持,覆盖率达到 93%,
(2)不足之处在于:defer 只作用于脚本文件,对于样式、图片等资源就无能为力了,并且 defer 加载的资源是要执行的,而 preload 则脚本、图片、样式都支持加载,且只下载资源并不执行,待真正使用到才会执行文件。
(3)对于页面上主/首屏脚本,可以直接使用 defer 加载,而对于非首屏脚本/其它资源,可以采用 preload/prefeth 来进行加载。
6、使用案例
(1)提前加载字体文件。由于字体文件必须等到 CSSOM 构建完成并且作用到页面元素了才会开始加载,会导致页面字体样式闪动。所以要用 preload 显式告诉浏览器提前加载。假如字体文件在 CSS 生效之前下载完成,则可以完全消灭页面闪动效果。
(2)使用 preload 预加载第二屏的内容,在网页开发中,对于非首屏部分采用懒加载是我们页面常用的优化手段,所以我们在页面 onload 之后可以通过 preload 来加载次屏所需要的资源,在用户浏览完首屏内容滚动时能够更快地看到次屏的内容。
(3)在页面加载完成之后,可以分析页面上所有的链接,判断用户可能会点击的页面,分析提取下一跳页面上所有的资源使用 prefetch 进行加载(这里不使用 preload,因为不一定会点击),浏览器会在空闲地时候进行加载,当用户点击链接命中了缓存,这可以有效地提升下一页面的首屏渲染时间。
(4)对于商品列表页面,在用户鼠标停留在某个商品的时候,可以去分析商品详情页所需要的资源并提前开启 preload 加载,跟第 3 点类似,都是用来预测用户的行为并且做出一些预加载的手段,区别在于当用户停留在商品上时,点击命中率更高,preload 可以立即加载资源,有效提升缓存命中率。