DTeam 技术日志

Doer、Delivery、Dream

前端性能优化-preload,prefetch,preconnect

小纪同学 Posted at — Aug 10, 2021 阅读

提到前端性能优化,我们通常会想到启用压缩,压缩资源文件大小。或者启用浏览器缓存,可以起到较少 HTTP 请求,优化资源加载速度的效果,但这些手段主要提升重复访问相同资源时的加载速度。默认情况下,浏览器只会先加载 HTML 中声明的资源。如果没有声明,浏览器是不会提前加载资源的。那有没有什么办法能提前加载页面所需资源,优化首次的加载速度呢?

很幸运,随着 Web 技术的发展,现代的浏览器可以做到提前加载页面所需资源了。使用资源提示伪指令 prefetch 和 preload,可以提前告知浏览器加载资源,从而可以缩短网站的(首次)加载速度,优化页面性能。

preload

一种预加载的方式,它通过声明向浏览器声明一个需要提交加载的资源,当资源真正被使用的时候立即执行,就无需等待网络的消耗。

使用方式有三种:

<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">

<!-- 用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = '/path/to/style.css';
document.head.appendChild(link);
</script>

<!--  HTTP 响应头中加上 preload 字段 -->
Link: <https://example.com/other/styles.css>; rel=preload; as=style

rel 属性值为 preload;as 属性用于规定资源的类型,并根据资源类型设置Accep请求头,以便能够使用正常的策略去请求对应的资源;href 为资源请求地址;onload 和onerror 则分别是资源加载成功和失败后的回调函数;

as 值包括:style、script、image、font、fetch、document、audio、video等。 如果请求跨域资源,需要加上crossorigin属性。

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

特点

使用场景

1. 提前加载字体

web 中字体属于较晚加载的一类资源,但是字体的渲染对用户体验来说至关重要。如果加载慢了会给用户造成字体抖动的现象,即字体会先显示成环境默认字体。

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

2. 加载第三方js

比如加载一段统计页面访问量的代码,但又不愿意这段代码的加载给页面渲染造成延迟从而影响用户体验,不想延迟 window 的 onload 事件。

<link rel="preload" as="script" href="async_script.js"
    onload="var script = document.createElement('script'); 
    script.src = this.href; document.body.appendChild(script);">

3. 响应式加载

如果 web 端支持手机浏览器访问,对于 web 端和手机端加载不同资源时,可以使用 link 的 media 属性来判断。常见的做法是通过 JS 判断当前浏览器类型动态地加载资源,但这样一来,浏览器的预加载器就无法及时发现他们,可能耽误加载时机,影响用户体验。

<link rel="preload" as="image" href="map.png" media="(max-width: 600px)">
<link rel="preload" as="script" href="map.js" media="(min-width: 601px)">

4. 预测用户行为提前加载资源

比如用户鼠标停留在某个 item 上,例如商品。去分析商品详情页所需要的资源并提前开启 preload 加载。

兼容性

preload 兼容性

可以通过下面方法判断是否支持 preload:

const isPreloadSupported = () => { 
  const link = document.createElement('link'); 
  const relList = link.relList; 
  if (!relList || !relList.supports) { 
    return false;  
  } 
  return relList.supports('preload'); 
};

preload 会使资源优先加载

浏览器加载资源优先级如下: 优先级

preload 用 “as” 或者用 “type” 属性来表示他们请求资源的优先级(比如说 preload 使用 as=”style” 属性将获得最高的优先级)。没有 “as” 属性的将被看作异步请求,“Early”意味着在所有未被预加载的图片请求之前被请求(“late”意味着之后)。

可以在 chrome 查看资源优先级: 资源优先级

注意

避免滥用 preload

若不确定资源是必定会加载的,则不要错误使用 preload,以免本末倒置,给页面带来更沉重的负担。当 preload 加载的资源没有使用时,浏览器会给出警告:

警告

preload 加载跨域资源时需要添加 crossorigin 属性

<link rel="preload" as="font" href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff">
<link rel="preload" as="font" crossorigin href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff">

如果对跨域的文件进行 preload 的时候,没有添加 crossorigin 属性,将会请求两次。

prefetch

prefetch 作用是告诉浏览器未来可能会使用到的某个资源,比如下一页。浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源。它的用法跟 preload 是一样的。

<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="prefetch" href="/path/to/style.css" as="style">

<!-- 用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement('link');
link.rel = 'prefetch';
link.as = 'style';
link.href = '/path/to/style.css';
document.head.appendChild(link);
</script>

<!--  HTTP 响应头中加上 prefetch 字段 -->
Link: <https://example.com/other/styles.css>; rel=prefetch; as=style

兼容性

prefetch 兼容性

preload 和 prefetch 对比

1、Chrome有四种缓存:http cache、memory cache、Service Worker cache 和 Push cache。在 preload 或 prefetch 的资源加载时,两者均存储在 http cache。当资源加载完成后,如果资源是可以被缓存的,那么其被存储在http cache中等待后续使用;如果资源不可被缓存,那么其在被使用前均存储在 memory cache。

2、preload 和 prefetch 都没有同域名的限制;

3、preload 主要用于预加载当前页面需要的资源;而prefetch主要用于加载将来页面肯能需要的资源;

4、不论资源是否可以缓存,prefetch 会在网络堆栈中至少缓存5分钟;

5、preload 需要使用 as 属性指定特定的资源类型以便浏览器为其分配一定的优先级,并能够正确加载资源。

避免混用 preload 和 prefetch

preload 和 prefetch 混用的话,并不会复用资源,而是会重复加载。

<link rel="preload"   href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff" as="font">
<link rel="prefetch"  href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff" as="font"> 

资源重复加载

preconnect

preconnect 允许浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,这包括 DNS 解析,TLS 协商,TCP 握手,这消除了往返延迟并为用户节省了时间。

“Preconnect 是优化的重要手段,它可以减少很多请求中的往返路径 —— 在某些情况下可以减少数百或者数千毫秒的延迟。”—— lya Grigorik(Web性能权威指南作者)

preconnect

它的用法跟 preload 是一样的。

<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preconnect" href="https://cdn.domain.com" as="style" crossorigin>

<!-- 用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement('link');
link.rel = 'preconnect';
link.as = 'style';
link.href = 'https://cdn.domain.com';
document.head.appendChild(link);
</script>

<!--  HTTP 响应头中加上 preconnect 字段 -->
Link: <https://cdn.domain.com>; rel=preconnect; as=style

下面是为 Google Fonts 使用 preconnect 的例子,通过给 fonts.gstatic.com 加入 preconnect 提示,浏览器将立刻发起请求,和 CSS 请求并行执行。在这个场景下,preconnect 从关键路径中消除了三个 RTTs(Round-Trip Time) 并减少了超过半秒的延迟,lya Grigorik 的 eliminating RTTS with preconnect 一文中有更详细的分析。

<link href='https://fonts.gstatic.com' rel='preconnect' crossorigin>
<link href='https://fonts.googleapis.com/css?family=Roboto+Slab:700|Open+Sans' rel='stylesheet'>

可以看到第二次请求时,节约了 400ms 左右的时间:

preconnect 请求

兼容性

preconnect 兼容性

案例

  1. Housing.com 在对他们的渐进式 Web 应用程序的脚本转用 proload 看到大约缩短了10%的可交互时间。

Housing.com

  1. Shopify 在转用 preload 加载字体后在 Chrome 桌面版获得了 50%(1.2s) 的文字渲染优化,这完全解决了他们的文字闪动问题。 Shopify

  2. 金融时报在它们的网站使用 preload HTTP 头时,他们节约了大约 1s 的显示片头图片时间。 金融时报

总结

preload,prefetch 以及 preconnect 能让浏览器提前加载需要的资源,将资源的下载和执行分离开来,运用得当的话可以对首屏渲染带来不小的提升,可以对页面交互上带来极致的体验。


友情链接


相关文章