最近在研究 vue-cli 3.0生成的工程,在构建后生成的 index.html里面发现了下面这种用法:css
<link as=style href=/css/app.f60416c7.css rel=preload>复制代码
<link as=script href=/js/app.69189fdd.js rel=preload>复制代码
这就触到了本人的知识盲区了,本着扫盲的目的,研究了下 link 标签,发现这个小东西功能仍是挺强大的,上面的就是为了实现预加载功能,懂点儿英文的,一看见preload 就大体知道了。html
以前也有预加载技术,像 prefetch,subresource 等,关于这二者和 preload 的区别,这是另外的话题了, 感兴趣的能够本身搜一下,不想搜的,你只要知道这两个跟 preload 相比弱的一逼就好了,就是 prefetch 浏览器兼容性方面稍微好一点点,这三个也各有偏重和应用场景,就不详细介绍了,下面咱们就详细展开preload 这块。前端
preload 是一项新的 web 标准,旨在提升性能,让 FE 对加载的控制更加粒度化。它让开发者有自定义加载逻辑的能力,免受基于脚本的加载器所带来的性能损耗。vue
preload 一个基本的用法就是提早加载资源,尽管大多数基于标记语言的资源能被浏览器的预加载器(preloader)尽早发现,但不是全部的资源都是基于标记语言的,好比一些隐藏在 css 和 js 中的资源(字体,图片等),当浏览器发现页面须要这些资源时,从新走一遍加载执行渲染的过程,会下降用户体验,而且对页面的渲染 形成延迟;git
Preloader 简介
HTML 解析器在建立 DOM 时若是碰上同步脚本(synchronous script),解析器会中止建立 DOM,转而去执行脚本。因此,若是资源的获取只发生在解析器建立 DOM时,同步脚本的介入将使网络处于空置状态,尤为是对外部脚本资源来讲,固然,页面内的脚本有时也会致使延迟。
预加载器(Preloader)的出现就是为了优化这个过程,预加载器经过分析浏览器对 HTML 文档的早期解析结果(这一阶段叫作“令牌化(tokenization)”),找到可能包含资源的标签(tag),并将这些资源的 URL 收集起来。令牌化阶段的输出将会送到真正的 HTML 解析器手中,而收集起来的资源 URLs 会和资源类型一块儿被送到读取器(fetcher)手中,读取器会根据这些资源对页面加载速度的影响进行有次序地加载。
预加载的好处:github
属性名web |
取值范围vue-cli |
介绍json |
as跨域 |
script:js 脚本font:字体文件style:样式表audio:音频video: 视频document:将被嵌入到<frame>或<iframe>元素内部的页面image: 图片fetch:将要经过 fetch 和 XHR 请求获取的资源好比jsonobject: 将被嵌入到<embed >元素内的文件worker:js 的 web worker 或 share worker |
该属性仅在 link 元素设置了rel=preload 是才能使用。规定了 link 元素要预加载的资源的类型,其取值范围也限制了哪些资源才可被预加载。设置了此属性使浏览器可以:1,更精确地优化资源加载优先级。2,匹配将来的加载需求,在适当的状况下,重复利用同一资源。3,为资源应用正确的内容安全策略。4,为资源设置正确的 Accept 请求头。 |
href |
<url> |
指定要加载资源的 URL,可使绝对地址也能够是相对地址 |
rel |
preload(当前功能相关) |
此属性用于指明被连接的资源相对于当前页面的关系。属性值必定是被空格分开的连接类型值。这个属性最多见的取值是:stylesheet,代表被链接资源对当前文档来讲是一个层叠样式表。当前取值 preload 代表连接资源是一个预加载的资源 |
type |
MIME涵盖类型 |
连接资源的 MIME 类型,在浏览器进行预加载到时候,这个属性将会很是有用,浏览器将使用 type 属性来判断它是否支持这一资源类型,若是支持,将正常预加载,下载将开始,不然对其忽略。 |
crossorigin |
|
加载字体文件的时候须要用到,详情往下看 |
<link>元素有一个很棒的特性是它们可以接受一个media属性。它们能够接受媒体类型或有效的媒体查询做为属性值,这将令你可以使用响应式的预加载!
让咱们来看一个简单的示例(能够查看Github上的源代码或在线示例):
<head> <meta charset="utf-8"> <title>Responsive preload example</title> <link rel="preload" href="bg-image-narrow.png" as="image" media="(max-width: 600px)"> <link rel="preload" href="bg-image-wide.png" as="image" media="(min-width: 601px)"> <link rel="stylesheet" href="main.css"></head><body> <header> <h1>My site</h1> </header> <script> var mediaQueryList = window.matchMedia("(max-width: 600px)"); var header = document.querySelector('header'); if(mediaQueryList.matches) { header.style.backgroundImage = 'url(bg-image-narrow.png)'; } else { header.style.backgroundImage = 'url(bg-image-wide.png)'; } </script></body>复制代码
你能够看到咱们在<link>元素中包含了一个media属性,所以,当用户在使用较窄屏幕的设备时,较窄的图片将会被预加载,而在较宽的设备上,较宽的图片将被预加载。而后咱们仍须要在header元素上附加合适的图片——经过Window.matchMedia / MediaQueryList 来加以实现(能够查看Testing media queries一文来了解更多信息)。
web 字体是较晚才能被发现的关键资源中常见的一种。可是在用户体验对前端来讲相当重要的现阶段前端开发来讲,web 字体对页面的渲染也是相当重要。字体的引用被深埋在 css 中,即使预加载器有提早解析 css,也没法肯定包含字体信息的选择器是否会真正做用在 dom 节点上。因此为了减小 FOUT(无样式字体闪烁,flash of unstyled text )须要预加载字体文件,有了 preload,一行代码搞定:
<link rel=preload href='font.woff2' as=font type='font/woff2' crossorigin />复制代码
NOTE :
crossorigin 属性在加载字体的时候是必须的,即使字体没有跨域是在本身公司的服务器上,由于用户代理必须采用匿名模式来获取字体资源( 为何会这样呢?)。
type 属性能够确保浏览器只获取本身支持的资源。
另一个有意思的场景也由于 preload 的出现变得可能——当你想加载某一资源但却不想执行它。好比说,你想在页面生命周期的某一时刻执行一段脚本,而你没法对这段脚本作任何修改,不可能为它建立一个所谓的 runNow()函数。
在 preload 出现以前,你能作的颇有限。若是你的方法是在但愿脚本执行的位置插入脚本,因为脚本只有在加载完成之后才能被浏览器执行,也就是说你得等上一下子。若是采用 XHR 提早加载脚本,浏览器会拒绝重用这段脚本,有些状况下,你可使用 eval 函数来执行这段脚本,但该方法并不老是行得通,也不是彻底没有反作用。
如今有了 preload,一切变得可能
var link = document.createElement("link");复制代码
link.href = "myscript.js";复制代码
link.rel = "preload";复制代码
link.as = "script";复制代码
document.head.appendChild(link);复制代码
上面这段代码可让你预先加载脚本,下面这段代码可让脚本执行
var script = document.createElement("script");复制代码
script.src = "myscript.js";复制代码
document.body.appendChild(script);
复制代码
先看代码
<link rel="preload" as="style" href="asyncstyle.css" onload="this.rel='stylesheet'">复制代码
preload 的 onload 事件能够在资源加载完成后修改 rel 属性,从而实现很是酷的异步资源加载。
脚本也能够采用这种方法实现异步加载
难道咱们不是已经有了<script async>? <scirpt async>虽好,但却会阻塞 window 的 onload 事件。某些状况下,你可能但愿这样,但总有一些状况你不但愿阻塞 window 的 onload 。
举个例子,你想尽量快的加载一段统计页面访问量的代码,但又不肯意这段代码的加载给页面渲染形成延迟从而影响用户体验,关键是,你不想延迟 window 的 onload 事件。
有了preload, 分分钟搞定。
<link rel="preload" as="script" href="async_script.js"复制代码
onload="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">复制代码
preload 是一个link,根据规范有一个media 属性(如今 Chrome 还不支持,不过快了),该属性使得选择性加载成为可能。
有什么用处呢?假设你的站点同时支持桌面和移动端的访问,在使用桌面浏览器访问时,你但愿呈现一张可交互的大地图,而在移动端,一张较小的静态地图就足够了。
你确定不想同时加载两个资源,如今常见的作法是经过 JS 判断当前浏览器类型动态地加载资源,但这样一来,浏览器的预加载器就没法及时发现他们,可能耽误加载时机,影响用户体验和 SpeedIndex 评分。
怎样才能让浏览器尽量早的发现这些资源呢?仍是 Preload!
经过 Preload,咱们能够提早加载资源,利用 media 属性,浏览器只会加载须要的资源。
<link rel="preload" as="image" href="map.png" media="(max-width: 600px)">复制代码
<link rel="preload" as="script" href="map.js" media="(min-width: 601px)">复制代码
Preload 还有一个特性是其能够经过 HTTP 头信息被呈现。也就是说上文中大多数的基于标记语言的声明能够经过 HTTP 响应头实现。(惟一的例外是有 onload 事件的例子,咱们不可能在 HTTP 头信息中定义事件处理函数。)
Link: <thing_to_load.js>;rel="preload";as="script"复制代码
Link: <thing_to_load.woff2>;rel="preload";as="font";crossorigin复制代码
这一方式在有些场景尤为有用,好比,当负责优化的人员与页面开发人员不是同一人时(也就是说优化人员可能没法或者不想修改页面代码),还有一个杰出的例子是外部优化引擎(External optimization engine),该引擎对内容进行扫描并优化。
前面全部的列子都基于一种假设——浏览器必定程度上支持 preload,至少实现了脚本和样式加载等基本功能。但若是这个假设不成立了。一切都将是然并卵。
为了判断浏览器是否支持 preload,咱们修改了 DOM 的规范从而可以获知 rel 支持那些值(是否支持 rel=‘preload’)。
至于如何进行检查,原文中没有,但 Github有一段代码可供参考。
var DOMTokenListSupports = function(tokenList, token) {复制代码
if (!tokenList || !tokenList.supports) {复制代码
return;复制代码
}复制代码
try {复制代码
return tokenList.supports(token);复制代码
} catch (e) {复制代码
if (e instanceof TypeError) {复制代码
console.log("The DOMTokenList doesn't have a supported tokens list");复制代码
} else {复制代码
console.error("That shouldn't have happened");复制代码
}复制代码
}复制代码
};复制代码
复制代码
var linkSupportsPreload = DOMTokenListSupports(document.createElement("link").relList, "preload");复制代码
if (!linkSupportsPreload) {复制代码
// Dynamically load the things that relied on preload.复制代码
}复制代码
caniuse.com 网站上显示浏览器版本支持状况以下,目前仍是比较高版本的浏览器会支持此功能,不过你们也不要担忧,在不支持的浏览器环境中,这部分标签会被忽略,能够作到平稳降级。