性能优化之 Preload
认领人须知
- 需要研究下 W3C 的标准,Preload 的基本原理
- 考虑下这个功能带来的弊端问题和滥用问题
😄. 我试试吧。
认领了这么久,也该写点文字来着。 Preload其实一项新的 web 标准,使得 web 开发者可以对加载细节做进一步控制,可以自定义加载逻辑。
Refers to a resource that should be loaded early in the processing of the link's context, without blocking rendering.
规范
地址: https://w3c.github.io/preload/ 注意:
- Preload 还在修订阶段,文档不断变化中。
- 目前只有 Chrome/Safari 高版本支持。
相关概念
- Prefetch: 获取可能会用到的资源,不同点是加载下一页面可能会用到的资源
- Subresource: 也是针对当前页面, 但是不是很成功,开发者无法控制资源的加载优先级,简单来说,就是想得美好,但是...嘿嘿嘿
属性
- as
- 浏览器能够正确设置资源优先级
- 能够确保发出的请求符合内容安全协议,不发无用的请求。
- 浏览器能够根据资源类型发送合适的接收请求头( Accept headers )
- 浏览器能通过资源类型来推断是否可以重用
- onload
- 该属性不会阻断窗口的 onload 事件( 除非请求的资源里有进行阻断 ).
使用场景
加载非标签资源
主要指藏在 CSS 或 JS 中的资源, as 则可以指明资源类型,因为浏览器不知道的类型往往加载优先级较低。
<!--
参见:https://fetch.spec.whatwg.org/#concept-request-destination
-->
<link rel="preload" href="late_discovered_thing.js" as="script">
加载字体
加载字体规则异常复杂,有些重要的字体等到真正加载的时候已经晚了。 PS: 即使符合同源策略,也需要加上 crossorigin 属性( 此规则也适用于加载 image )
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
只加载不执行
一般用于加载 JS 文件
var link = document.createElement("link");
link.href = "myscript.js";
link.rel = "preload";
link.as = "script";
document.head.appendChild(link);
基于标签的异步加载
<link rel="preload" as="style" href="async_style.CSS" onload="this.rel='stylesheet'">
响应式加载
<link rel="preload" as="image" href="map.png" media="(max-width: 600px)">
<link rel="preload" as="script" href="map.js" media="(min-width: 601px)">
注意事项
- 如果浏览器不支持,你得有 backup 策略
- 与 HTTP/2 相辅相成,并不是使用 HTTP/2 push 就不用使用 preload 了。
- Preload 更透明
- Preload 能加载第三方资源
- Preload 可以有 onload 事件
参考
这里分享就是对其进行简单的总结:
下次探讨内容预告
- 标准解析
- 实例 demo
关于标准
看标准是一件很头疼的事情,不过看完之后你会有顿然开朗的感觉,Preload 的前世今生都在那里, 这里,分享自己对标准做的一个"简短"的解读,不足之处,还望指正。
Why Preload
在开发中,很多人碰到过这样的情况,有些资源虽然不会立即执行,但是希望需要尽早获取,要是等到需要用时再去加载,需要晚点加载的原因是如依赖管理、条件加载、加载顺序控制等等。在之前,你要做到这些,是需要有额外的性能花费的。 首先,看看大家采用的方案:
- 动态插入元素(e.g. img, script, link)
- 缺陷:script 并不能延迟执行
- 使用 XMLHttpRequest 进行异步加载
- 缺陷1: 浏览器无法根据资源类型进行预判并优化加载,造成性能问题
- 缺陷2: 大量的加载脚本会阻塞页面的加载,产生严重的性能问题.
Preload 则很好的解决了这些问题:
- 不阻塞式加载,与页面加载逻辑分离
- 资源提前预判,加载迅速
- 优先级高,不像 Prefetch/Subresource 那般模棱两可
使用示例
<!-- 通过标签加载 -->
<link rel="preload" href="/styles/other.css" as="style">
<!-- 通过 JS 动态插入 -->
<script>
var res = document.createElement("link");
res.rel = "preload";
res.as = "style";
res.href = "styles/other.css";
document.head.appendChild(res);
</script>
加载资源的时机
开发者
- 首先,浏览器得支持 Preload
- Preload link 标签已插入至 document
- Preload link 标签在 document 中存在
- href 属性改变
- 设置/修改/移除 crossorigin 属性时
- as 指定的资源不再是之前的资源时
- as 指定的资源之前没有加载,当前发生改变
- type 指定的资源之前没有加载(MIME不支持等),当前发生改变
- media 指定的资源支持没有加载(资源无效),当前发生改变
浏览器
如果 href 属性被重置,浏览器当立即停止当前加载,浏览需要做的一些动作可以分为以下几个步骤:
- 如果 href 属性被重置为空, 浏览器立即停止加载.
- 解析 URL
- 如果前两步失败,立即停止加载
- 解析 as 属性,如果没有填写则置为空字符串,如果 as 指定资源类型无效,则抛出错误,你可以用 onerror 属性捕获到
- 解析 type 属性,如果没有填写则置为空字符串,并开始下一步骤; 如果填写了但是不是有效的 MIME 类型,那么立即停止加载
- 解析 media 属性,如果没有填写则置为空字符串,并开始下一步骤; 如果填写了但是不是有效的媒体类型类型,那么立即停止加载
- 以跨域方式访问 URL 指定的资源。 请求头中的 origin 属性指定为当前页,原来默认的 origin 置为 taint
一些要求
- 浏览器加载 Preload link 元素时不能影响当前页 dom 元素的加载
- 获取资源时默认按照同源策略加载,资源信息由 as 指定,需要as 中的内容会写到请求头中,如果不指定 as,那么跟 XMLHttpRequest 请求方式并无二样。
资源加载完成
浏览器需要有以下几个动作
- 成功加载时触发 load 事件,失败加载时出发 error 事件
- 将资源置入缓存
- 浏览器会有自己的一套缓存逻辑,这里不做讨论
- 理论上必须放入 http 资源缓存与内存( 便于当前页使用 )中。
- 确保不在当前上下文执行加载下来的资源,此外,必须确保资源不会被重复加载。
Link 元素接口拓展
这小结反复强调 as 的重要性,不指定 as 就用不上 Preload
partial interface HTMLLinkElement {
[CEReactions] attribute DOMString as;
};
as 必须是准确无误,否则 preload 无法正确执行相关逻辑,具体可以参考上篇中所述的特性 as 资源类型可以有下面这些:
- image
- script
- media
- worker
- embed
- object
- font
- style
- document( 指iframe/frame )
Server Push( HTTP2 )
上次探讨我们说到, Preload 和 HTTP/2 Server Push 其实是相辅相成的,事实上, 从浏览器角度来说,你用 Preload 与 Server Push 获取资源其实并无两样。 Preload 提供了一个 nopush 属性,我们可以和服务器约定是否进行 Server Push,而开发者只需要简单的加一个属性 nopush 进行标记就好了。 如果你不加,浏览器就会把 Preload 指定的资源作为 Server Push 的候选项了。
<link rel="preload" href="/app/style.css" as="style" nopush>
<link rel="preload" href="/app/script.js" as="script" nopush>
非规范内容
(未完待续)
下次探讨内容预告
- 直接上实例 demo 啦~
参考
var preload = document.createElement("link");
link.href = "myscript.js";
link.rel = "preload";
link.as = "script";
这个是不是写错了?link应该改成preload?