页面资源预加载(preload)和预获取(prefetch)

2020年01月06日Web前端

用了Gatsby也有一段时间了,静态化博客速度也是让我很是满意。这几天在检查seo属性配置时,发现了几个奇怪的标签属性,见是见过,但没这么用过,link标签配了rel="preload"和rel="prefetch",我们常用的link不是用来加载css的么?rel属性不也是stylesheet么?

preload是直接显示在head中的,

博客preload

而prefetch则是根据滚动条滚动后append的。

博客prefetch

preload

预加载,即提前加载页面中需要的资源。他的机制是在页面加载的早期阶段就开始获取,在浏览器的主渲染机制介入前就进行预加载。这样使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。

除了机制优势外,对于隐藏在css或js中的资源(css背景图,js操作样式,html中视频资源等等),提前获取,可以避免资源加载造成对页面渲染的延迟。

<link rel="preload" href="./js/1.js" as="script" />

和普通加载css方式类似,不过rel为preload,href则是资源的地址。

as属性

相对于常用的link方式多了个as属性。as属性是用来指定将要预加载的内容的类型,这样可以让浏览器:

  • 更精确地优化资源加载优先级。
  • 匹配未来的加载需求,在适当的情况下,重复利用同一资源。
  • 为资源应用正确的内容安全策略。
  • 为资源设置正确的 Accept 请求头。

常用的as属性如下:

  • audio: 音频文件。
  • document: 一个将要被嵌入到<frame>或<iframe>内部的HTML文档。
  • embed: 一个将要被嵌入到元素内部的资源。
  • fetch: 那些将要通过fetch和XHR请求来获取的资源,比如一个ArrayBuffer或JSON文件。
  • font: 字体文件。
  • image: 图片文件。
  • object: 一个将会被嵌入到元素内的文件。
  • script: JavaScript文件。
  • style: 样式表。
  • track: WebVTT文件。
  • worker: 一个JavaScript的web worker或shared worker。
  • video: 视频文件。

注:在chrome79下测试,as属性必须有,否则该link会被忽略。

type属性

type属性表示预加载资源的MIME类型,浏览器将根据type属性来判断它是否支持这一资源,若支持,则会下载,否则便忽略。

as属性只是用来表示资源优先级的,而type才是标记资源类型的,所以当为了更高优先级,我们可以这么写:

<link rel="preload" href="./js/1.js" as="style" />

js文件设置了style类型,提升了优先级。(至于资源优先级,请看这篇文章: Chrome中的资源优先级

crossorigin属性

当提前预加载的是字体时,就需要该属性(即使不是跨域时)。

<link rel="preload" href="./font/iconfont.ttf" as="font" type="font/ttf" crossorigin="anonymous" />

增加了type后,不同浏览器将仅会对自己支持的字体进行下载。crossorigin则去处理CORS的问题。

更多信息可以查看Font fetching requirements

media属性

link元素还支持media属性,他能够支持媒体查询。

<link rel="preload" href="./css/bg-1.css" as="style" media="(min-width: 1000px)">
<link rel="preload" href="./css/bg-2.css" as="style" media="(max-width: 1001px)">

他根据当前浏览器窗口的分辨率,预加载不同的css文件。(与media query不一样的是,resize窗口,并不会再去加载另一个样式文件)

兼容性

新特性虽是好用,但是兼容性查看都是绕不过去的,通过caniuse

preload兼容性

图上显示57以上是不支持的,实际是浏览器默认关闭了。实测chrome79是支持的,不过as属性不能省。

关于检测浏览器是否支持preload:

// 是否支持preload
const preloadSupported = () => {
    const link = document.createElement('link');
    const relList = link.relList;

    if (!relList || !relList.supports) {
        return false;
    }
    return relList.supports('preload');
}

脚本化

借助js操作dom,我们可以更灵活的实现资源的预加载和执行:

// 手动预加载
var preloadLink = document.createElement("link");
preloadLink.href = "myscript.js";
preloadLink.rel = "preload";
preloadLink.as = "script";
document.head.appendChild(preloadLink);

这样的话,浏览器仅仅只是加载了该脚本,并没有执行。

当需要执行时,只要执行:

// 执行预加载的资源
var preloadedScript = document.createElement("script");
preloadedScript.src = "myscript.js";
document.body.appendChild(preloadedScript);

preload会阻碍load事件么

实际测试当preload的文件在文档中有直接使用时,是会有影响的。

<head>
  <link rel="preload" href="./js/2.js" as="script" />
</head>
<script src="./js/2.js"></script>

这样是会有影响的,当底部没有script使用时,则不会有影响。

prefetch

prefetch是用来告诉浏览器下载稍后用户会用到的资源,所以他的优先级很低,而且下载过程也是在浏览器空闲时。当然,这也意味着prefetch的资源可能是没有用的,因为用户并不会按照你的期望去进入某些页面。

<link rel="prefetch" href="page-data.json" />

prefetch提前加载的资源不是当前页面的,利用http缓存,当用户进入到既定页面时,请求资源通过http强缓存被命中后,达到加快显示的目的。

混用

1. prefetch的资源同时在当前页面被使用

<head>
    <link rel="prefetch" href="./js/1.js">
</head>
<body></body>
<script src="./js/1.js"></script>

如此操作只会多次请求,得不偿失(脚本只会执行一次):

两次获取资源

2. 与preload混用

<link rel="prefetch" href="./js/1.js">
<link rel="preload" href="./js/1.js" as="script">

同样的,该js文件会被获取两次,浪费了带宽。

兼容性

prefetch兼容性

prefetch的兼容性相对于preload要好些。

总结

preload针对当前页面即将要使用的资源进行提前加载,prefetch对潜在页面的资源进行低优先级的加载。即,preload针对当前页面,prefetch是针对其他页面,两者完全不同,不可替代。

当资源被preload或者prefetch后,会根据其HTTP状态确定是否被缓存(cache-control 和 max-age),若可以,它将存储在HTTP缓存中,可用于当前和未来的会话。 如果资源不可缓存,则不会将其存储在HTTP缓存中。 相反,它会被缓存到内存缓存中并保持不变直到它被使用。

简单demo见:Github