performance-column icon indicating copy to clipboard operation
performance-column copied to clipboard

性能优化之 Preload

Open barretlee opened this issue 8 years ago • 4 comments

认领人须知

  • 需要研究下 W3C 的标准,Preload 的基本原理
  • 考虑下这个功能带来的弊端问题和滥用问题

barretlee avatar Mar 02 '17 07:03 barretlee

😄. 我试试吧。

pengkobe avatar Mar 02 '17 11:03 pengkobe

认领了这么久,也该写点文字来着。 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 高版本支持。

相关概念

  1. Prefetch: 获取可能会用到的资源,不同点是加载下一页面可能会用到的资源
  2. 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

pengkobe avatar Mar 06 '17 11:03 pengkobe

关于标准

看标准是一件很头疼的事情,不过看完之后你会有顿然开朗的感觉,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>

加载资源的时机

开发者

  1. 首先,浏览器得支持 Preload
  2. Preload link 标签已插入至 document
  3. Preload link 标签在 document 中存在
  4. href 属性改变
  5. 设置/修改/移除 crossorigin 属性时
  6. as 指定的资源不再是之前的资源时
  7. as 指定的资源之前没有加载,当前发生改变
  8. type 指定的资源之前没有加载(MIME不支持等),当前发生改变
  9. media 指定的资源支持没有加载(资源无效),当前发生改变

浏览器

如果 href 属性被重置,浏览器当立即停止当前加载,浏览需要做的一些动作可以分为以下几个步骤:

  1. 如果 href 属性被重置为空, 浏览器立即停止加载.
  2. 解析 URL
  3. 如果前两步失败,立即停止加载
  4. 解析 as 属性,如果没有填写则置为空字符串,如果 as 指定资源类型无效,则抛出错误,你可以用 onerror 属性捕获到
  5. 解析 type 属性,如果没有填写则置为空字符串,并开始下一步骤; 如果填写了但是不是有效的 MIME 类型,那么立即停止加载
  6. 解析 media 属性,如果没有填写则置为空字符串,并开始下一步骤; 如果填写了但是不是有效的媒体类型类型,那么立即停止加载
  7. 以跨域方式访问 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 啦~

参考

pengkobe avatar Mar 16 '17 01:03 pengkobe

var preload = document.createElement("link");
link.href = "myscript.js";
link.rel = "preload";
link.as = "script";

这个是不是写错了?link应该改成preload?

fengzi2016 avatar Nov 23 '18 07:11 fengzi2016