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

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

什么是<link rel=”prefetch“>?

prefetch (预取用),它可以利用浏览器的空闲时间来预取用(下载)用户可能在不久的将来会访问的资源。换句话说,浏览器将提前加载用户将来可能要访问的页面资源。浏览器将这些提前下载的资源存储在本地缓存中,以便在用户最终访问该页面的资源时能更快地发送请求的信息,并非常快速的加载资源。所以,使用 prefetch 技术,不会减少 HTTP 请求,但会提升使用资源时的资源加载速度。

Prefetch 使用场景和注意事项

prefetch 预取用的资源在加载完成后,并不会像加载普通的资源那样,加载完成后浏览器不会马上解析资源,而只是缓存到本地。不立即执行解析,特别是不立即解析脚本文件,就意味着不会阻塞页面加载其它资源。只有用户访问了调用这些资源的 页面的时候,才会立刻解析。也就是说,prefetch 的使用场景是用来提前加载那些用户将来会访问的页面资源,优化将来会访问页面资源的首次加载速度。

具体的场景例如:

  • 当一系列页面很像幻灯片时,请加载接下来的1-3页,之前的1-3页(假设它们不占很大)。
  • 加载要在网站上大多数页面上使用的图像。
  • 将搜索结果的下一页加载到您的网站上。

当然,使用 prefetch 预取用技术也有需要注意的事项。那就是当用户始终都没有访问 prefetch 预期在将来可能访问的页面资源时,使用 prefetch 就可能会花费额外的带宽(流量)。

Prefetch 浏览器支持情况

浏览器对 prefetch 的支持情况还是不错的,除了 Safari 浏览器目前还不支持,其余的主流浏览器都已经支持了,甚至连 IE11 都支持了。而且在不支持 prefetch 的浏览器中,浏览器只会忽略它,不会带来额外的影响。所以使用 prefetch 优化性能,遵循了渐进式用户体验提升的原则。

Prefetch 资源如何选择?

前文提到了,使用 prefetch 用来加载稍后会用到的资源,如果用户没有访问期望的页面,使用 prefetch 就可能会花费额外的带宽。那么我们应该怎么选择要加载的资源,避免额外消耗呢?

首先,我们需要知道,浏览器对 HTML 页面中调用的各种资源是有着不同级别的优先级(Priorty )区分的。在 Chrome 浏览器中,我们可以按 F12 键,打开 DevTools 工具面板,然后点击 Network 标签,接着右键点击 Name 标题栏,在弹出的菜单中选择 Priorty 选项(默认是不展示的),这样就可以看到浏览器的资源优先级的列数据了。

如图所示,我们可以看到优先级高的资源(显示为 Highest 或者 High 的资源)基本都是 CSS、JS和字体资源。那么我们使用 prefetch 加载资源也应该选择这些优先级高的资源。应该使用 prefetch 加载那些用户使用比较频繁的模块资源,这样用户接下来大概率会使用到这些资源,从而避免 prefetch 加载的资源用户没有使用,而造成额外的带宽消耗。

什么是<link rel=”preload“>?

preload (预加载),它告诉浏览器如何将特定资源提前提取到当前页面中。本质上,它会在当前页面开始加载之前在浏览器后台提前下载资源。并且,浏览器通常以中等优先级,而不是布局阻塞的方式来获取此资源。使用 preload 提前加载的资源,不会花费额外的带宽。也就是不会产生额外的 HTTP 请求,这个是 preload 与 prefetch 不同的地方之一。

Preload 使用场景和注意事项

preload 和 prefetch 类似,使用 preload 加载的资源在加载完成后浏览器也不会立刻解析。preload 预加载加载资源的使用场景是 preload 预加载当前访问页面会立刻使用到的资源。虽然使用 preload 提前加载的资源,不会花费额外的带宽,但如果 preload 预加载的资源,在加载完成后3秒钟后还未被使用,这时(Chrome)浏览器在控制台中会显示警告,提示预加载的资源在当前页面没有被引用。

Preload 浏览器支持情况

preload 比 prefetch 的 Web 标准更新(https://w3c.github.io/preload/)来得更晚,但可以看到,浏览器对 preload 的支持也是不错的,目前主流浏览器只有 IE11 不支持。同 prefetch 的情况一样,在不支持的浏览器中,也是直接忽略 preload 的,也不会产生任何负面的影响。也遵循了渐进式用户体验提升的原则。

Preload 资源如何选择?

preload 资源的选择规则就简单很多, prload 预加载当前页面就要用到的资源。当然,也是选择优先级高的资源。使用过 lighthouse 前端性能分析工具的同学应该都知道,在 lighthouse 性能优化指导规则中就有一项是“预加载关键请求”。指的就是使用 preload 预加载当前页面的优先级高(关键)的资源。

Preload 和 Prefetch 的调用方式

preload 和 prefetch 的使用方式一样,采用 <link rel=”prefetch“> 或者 <link rel=”preload“> 方式加载资源文件,废话不多说,直接上代码:

<head>
    <meta charset="utf-8">
    <title>JS and CSS preload example</title> 
    <!-- 在 header 区域加入 -->
    <link rel="preload" href="style.css" as="style">
    <link rel="preload" href="main.js" as="script">
    <link rel="prefetch" href="news.js" as="script">
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <h1>bouncing balls</h1> <canvas></canvas>
    <script src="main.js" defer></script>
</body>

可以看到,prefetch 和 preload 调用方式很简单。使用 <link> 标签,设置 rel 属性设置值为 preload 或者 prefetch, 标签成为我们想要的任何资源的预加载器。另外,还需要指定:

  • href:资源的路径;
  • as:资源的类型;

示例代码中 preload 和 prefetch 了 CSS 和 JavaScript 文件,并且在页面中就立刻调用了 preload 加载的资源。而 prefetch 的资源,在页面中则没有立刻被调用,prefetch 的 news.js 文件只是提前加载下来,保存到了浏览器的缓存中,以便稍后在访问 news (相关)页面时可以立即解析它。

Preload 和 Prefetch 支持哪些类型的资源?

除了 prefetch 和 preload CSS 和 JS 外,还可以加载很多其它类型的资源文件。prefetch 和 preload 可以支持许多不同的资源类型,在 <link> 元素中使用 as 属性设置资源类型,可选的值为:

  • audio:音频文件,通常在中使用<audio>。
  • document:打算由<frame>或嵌入的HTML文档<iframe>。浏览器未实现
  • embed:要嵌入<embed>元素内的资源。
  • fetch:通过提取或XHR请求访问的资源,例如ArrayBuffer或JSON文件。
  • font:字体文件。
  • image: 图像文件。
  • object:要嵌入<object>元素内的资源。
  • script:JavaScript文件。
  • style:CSS样式表。
  • track:WebVTT文件。
  • worker:JavaScript网络工作者或共享工作者。
  • video:视频文件,通常在中使用<video>。 浏览器未实现

另外,<link> 元素可以接受 type 属性,该属性包含元素指向的资源的 MIME 类型。这在预加载资源时特别有用,浏览器将使用 type 属性值来计算是否支持该资源,并且仅在支持的情况下才下载该属性,否则将忽略该属性。

<head>
    <meta charset="utf-8">
    <title>Video preload example</title>
    <link rel="preload" as="image" href="/favicon.ico" type="image/x-icon">
    <link rel="preload" as="image" href="/static/images/app.svg" type="image/x-svg">
    <link rel="preload" as="image" href="/static/images/logo.png" type="image/png">
    <link rel="preload" href="sintel-short.mp4" as="video" type="video/mp4">
    <link rel="preload" href="sintel-short.webm" as="video" type="video/webm">
</head>

<body> 
    <video controls>
        <source src="sintel-short.mp4" type="video/mp4">
        <source src="sintel-short.webm" type="video/webm">
        <p>Your browser doesn't support HTML5 video. Here is a <a href="sintel-short.mp4">link to the video</a> instead.
        </p>
    </video> 
</body>

在上面 DEMO 页面的使用场景下,支持 MP4 的浏览器将预加载并使用 MP4 格式的文件,从而有望使视频播放器对用户更流畅/响应更快。而不支持 MP4 的浏览器则仍然可以加载 WebM 版本的资源,但是无法获得预加载的优势。这个示例列举了如何将预加载的内容与渐进增强的原理相结合。

启用 CORS 资源提取

除了提取相同域名下的资源, preload 和 prefetch 还支持预加载其它域名的资源。启用 CORS 资源提取,例如:fetch(),XMLHttpRequest 或字体。这个时候,就需要特别注意,需要设置 crossorigin 属性到 <link> 标签。

<head>
    <meta charset="utf-8">
    <title>Web font example</title>
    <link rel="preload" href="fonts/cicle_fina-webfont.woff2" as="font" type="font/woff2" crossorigin>
    <link rel="preload" href="fonts/zantroke-webfont.woff2" as="font" type="font/woff2" crossorigin>
    <link href="style.css" rel="stylesheet">
</head>

<body> … </body>

需要特别注意的是,在调用跨域的字体资源的时候,即使获取的字体资源不是跨源的,也需要将属性设置为匹配资源的 CORS 和凭据模式。

使用 Preload 可以提升多少性能?

最后让我们来看看使用预加载可以带来多少性能提升?

无预载链接,styles.css并且ui.js只要求后,app.js已被下载,解析和执行。

如图所示,在没有使用 preload 加载资源前,浏览器在下载,解析和执行完页面中的 .js 文件(app.js)后,才会开始加载它之后的 2 个资源。也就是说下载解析 .js 文件,会阻碍浏览器加载在它之后的其它资源的加载解析。但是我们知道,图中 ui.js 和 style.css 这些资源也很重要,应该尽快下载。

带有预加载链接,styles.css、ui.js 和 app.js 同时请求。

如图所示,使用 preload 潜在的性能提升是基于声明了预加载链接,浏览器将能够在多早之前启动资源请求。例如,如果 app.js 花费 200 毫秒来下载,解析和执行,则每个资源的潜在节省为200毫秒,因为 app.js 它不再是每个请求的瓶颈,在加载 app.js 的时候不会阻塞其它资源的下载。而是会几乎同时加载这些资源文件,也就是这里至少能够节省200毫秒的资源加载速度。