前言

看天气预报,今天好多地方都开始下雪了。今日早读文章由@李斌分享。

正文从这开始~

Preload 作为一个新的web标准,旨在提高性能和为web开发人员提供更细粒度的加载控制。Preload使开发者能够自定义资源的加载逻辑,且无需忍受基于脚本的资源加载器带来的性能损失。

在 HTML 代码中,它看上去大概是下面这样的一段声明式获取指令(declaratiev fetch directive)。

<link rel=“preload”>

拿我们的话来说,通过这一方法我们告诉浏览器开始获取某一特定资源,毕竟我们是作者,知道浏览器很快就会用到这一资源。

与现有的类似技术的区别

事实上,关于预加载,我们已经有<link rel=“prefetch”>,而且浏览器支持情况还不错。


除此之外,Chrome还支持过<link rel=“subresource”>

但Preload与这两者不同,<link rel=“prefetch”>的作用是告诉浏览器加载下一页面可能会用到的资源,注意,是下一页面,而不是当前页面。因此该方法的加载优先级非常低(自然,相比当前页面所需的资源,未来可能会用到的资源就没那么重要了),也就是说该方式的作用是加速下一个页面的加载速度。

<link rel=“subresource”>的设计初衷是处理当前页面,但最后还是壮烈牺牲了。因为开发者无法控制资源的加载优先级,因此浏览器(其实也只有 Chrome 和基于 Chrome 的浏览器)在处理此类标签时,优先级很低,到底有多低呢?这么说吧,在大多数情况下,用了等于没用。

为什么 Preload 更好

Preload是为处理当前页面所生,这点和 subresource 一样,但他们之间有着细微且意义重大的区别。Preload 有 as 属性,这让浏览器可做一些 subresource 和 prefetch 无法实现的事:

浏览器可以设置正确的资源加载优先级,这种方式可以确保资源根据其重要性依次加载, 所以,Preload既不会影响重要资源的加载,又不会让次要资源影响自身的加载。

浏览器可以确保请求是符合内容安全策略的,比如,如果我们的安全策略是Content-Security-Policy: script-src 'self',只允许浏览器执行自家服务器的脚本,as 值为 script 的外部服务器资源就不会被加载。

浏览器能根据 as 的值发送适当的 Accept 头部信息

浏览器通过 as 值能得知资源类型,因此当获取的资源相同时,浏览器能够判断前面获取的资源是否能重用。

Preload 的与众不同还体现在 onload 事件上(至少在 Chrome 中,prefetch 和 subresource 是不支持的)。也就是说你可以定义资源加载完毕后的回调函数。

 <link rel="preload" href="..." as="..." onload="preloadFinished()">

此外,preload 不会阻塞 windows 的 onload 事件,除非,preload资源的请求刚好来自于会阻塞 window 加载的资源。

结合上面所有这些特征,preload 给我们带来了一些以前不可能实现的功能。

资源的提前加载:

preload 一个基本的用法是提前加载资源,尽管大多数基于标记语言的资源能被浏览器的预加载器(Preloader)尽早发现,但不是所有的资源都是基于标记语言的,比如一些隐藏在 CSS 和 Javascript 中的资源。当浏览器发现自己需要这些资源时已经为时已晚,所以大多数情况,这些资源的加载都会对页面渲染造成延迟。

Preloader 简介

HTML 解析器在创建 DOM 时如果碰上同步脚本(synchronous script),解析器会停止创建 DOM,转而去执行脚本。所以,如果资源的获取只发生在解析器创建 DOM时,同步脚本的介入将使网络处于空置状态,尤其是对外部脚本资源来说,当然,页面内的脚本有时也会导致延迟。

预加载器(Preloader)的出现就是为了优化这个过程,预加载器通过分析浏览器对 HTML 文档的早期解析结果(这一阶段叫做“令牌化(tokenization)”),找到可能包含资源的标签(tag),并将这些资源的 URL 收集起来。令牌化阶段的输出将会送到真正的 HTML 解析器手中,而收集起来的资源 URLs 会和资源类型一起被送到读取器(fetcher)手中,读取器会根据这些资源对页面加载速度的影响进行有次序地加载。

现在,有了 preload,你可以通过一段类似下面的代码对浏览器说,”嗨,浏览器!这个资源你后面会用到,现在就加载它吧。“

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

as 属性的作用是告诉浏览器被加载的是什么资源,可能的 as 值包括:

  • “script”

  • “style”

  • “image”

  • “media”

  • “document”

更多请参考fetch spec

忽略 as 属性,或者错误的 as 属性会使 preload 等同于 XHR 请求,浏览器不知道加载的是什么,因此会赋予此类资源非常低的加载优先级。

对字体的提前加载

web 字体是较晚才能被发现的关键资源(late-discovered critical resources)中常见的一类 。web 字体对页面文字的渲染资至关重要,但却被深埋 CSS 中,即便是预加载器有解析 CSS,也无法确定包含字体信息的选择器是否会真正应用在 DOM 节点上。理论上,这个问题可以被解决,但实际情况是没有一个浏览器解决了这个问题。而且,即便是问题得到了解决,浏览器能对字体文件做出合理的预加载,一旦有新的 css 规则覆盖了现有字体规则,前面的预加载就多余了。

总之,非常复杂。

但有了 preload 这个标准,简单的一段代码就能搞定字体的预加载。

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

需要注意的一点是:crossorigin 属性是必须的,即便是字体资源在自家服务器上,因为用户代理必须采用匿名模式来获取字体资源。

type 属性可以确保浏览器只获取自己支持的资源。尽管Chrome 支持 WOFF2,也是目前唯一支持 preload 的浏览器,但未来或许会有更多的浏览器支持 preload,而这些浏览器支不支持 WOFF2 就不好说了。

动态加载,但不执行

另外一个有意思的场景也因为 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>

虽好,但却会阻塞 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)">

HTTP 头

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),该引擎对内容进行扫描并优化。

特征检查 (Feature Detection)

前面所有的列子都基于一种假设——浏览器一定程度上支持 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.}

讨论地址:https://github.com/w3c/preload/issues/7

是否可以用 HTTP/2 Push 完成 preload 的工作?

当然不行,尽管有一些相同的特性,但总的来说,他们的关系是互补而不是取代。

HTTP/2 Push 的优势是能够主动推送资源给浏览器,也就是说,服务器甚至不需要等到资源请求就能将资源推送给浏览器。

而 Preload 的优势在于其加载过程是透明的,一旦资源加载完毕或出现异常,应用可以获得事件通知。这一点是 HTTP/2 Push所不具备的。另外,Preload 还能加载第三方资源,但 HTTP/2 Push 不能。

此外,HTTP/2 Push 没办法将浏览器的缓存和非全局 cookie (non-global cookie) 考虑进去。也就是说,服务器推送的内容可能已经存在于客户端的缓存中,从而导致毫无意义的网络传输。(不过一份新的规范旨在解决该问题——cache digest specification,Github 上的 一个轻量级 Web服务H2O器实现了该功能,H2O在1.5版中引入了基于cookie 的cache-aware server push,原理是在首次 Server Push 完成后,在客户端存一个指纹,服务端后续检查到指纹存在时,先在指纹中查询要 Push 的资源,没查到才推送),但是非全局的 cookie就没这么好运了。对于这类型的资源,Preload 才是你的朋友。

Preload还有一个HTTP/2 Push 所不具备的能力是可以进行内容协商(content negotiation),也就是说如果你想通过 Client-Hints或者 HTTP 头的 accept 信息获取最合适的资源格式,HTTP/2 Push 帮不了你。

优化核心依旧是减少下载时间

JS篇中的预先解析DNS(dns-prefetch)依旧适用,提前解析CSS文件所在域名的DNS。

Preload

因为CSS已经在head中,我们不需要为css加preload属性了,但是css中用到的字体文件,一定要在所有css之前proload上。

<link rel="preload" href="/webfont.woff2" as="font"> 

首页CSS内联,非必要CSS异步加载

首页用到的CSS内联写在<head>中,其余CSS均采用异步加载,可以采用这种自己实现的加载CSS的方法,在合适的需要时加载需要的css

function LoadStyle(url) {  try {    document.createStyleSheet(url)  } catch(e) {    var cssLink = document.createElement('link');    cssLink.rel = 'stylesheet';    cssLink.type = 'text/css';    cssLink.href = url;    var head = document.getElementsByTagName('head')[0];    head.appendChild(cssLink)  }}

如果你使用webpack,那就更轻松了,使用import函数,大致如下

// 在a.js模块中直接引入cssimport 'style.css'// 在需要a.js模块的地方improt('path-of-a.js').then(module => {})

webpack打包后,其实是把style.css打包进了a.js,在异步加载a.js的时候,会将style.css中的代码插入haed标签中。

终极完美结构

<!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>Faster</title>  <link rel="dns-prefetch" href="//cdn.cn/"><link rel="preload" href="//cdn.cn/webfont.woff2" as="font">  <link rel="preload" href="//cdn.cn/Page1-A.js" as="script">  <link rel="preload" href="//cdn.cn/Page1-B.js" as="script"><link rel="prefetch" href="//cdn.cn/Page2.js">  <link rel="prefetch" href="//cdn.cn/Page3.js">  <link rel="prefetch" href="//cdn.cn/Page4.js"><style type="text/css">    /* 首页用到的CSS内联 */  </style></head><body><script type="text/javascript" src="//cdn.cn/Page1-A.js" defer></script><script type="text/javascript" src="//cdn.cn/Page1-B.js" defer></script></body></html>

最后,为你推荐

【第1053期】Preload,Prefetch 和它们在 Chrome 之中的优先级

关于本文
作者:@李斌
原文:https://zhuanlan.zhihu.com/p/32561606

【第1155期】如何快速融入新团队?

【第1151期】技术的热门度曲线

【第1159期】CSS预加载Preload相关推荐

  1. 好看的css预加载旋转动画 与 流光字体

    今天刚好在做这个功能,就实现一个 预加载的动画效果,随手记录 一.预加载旋转动画 Html <view class="concentric_round"></vi ...

  2. prefetch 和preload_资源预加载preload和资源预读取prefetch简明学习

    前面的话 基于VUE的前端小站改造成SSR服务器端渲染后,HTML文档会自动使用preload和prefetch来预加载所需资源,本文将详细介绍preload和prefetch的使用 资源优先级 在介 ...

  3. GORM v2 关联预加载Preload和Joins的区别

    前言 本文中使用到的数据表结构以及GORM版本的区分详见以下文章:GORM v2 一对一关联查询使用(Belongs To .Has One) 执行区别 调用gorm的Debug方法打印一下一对一关联 ...

  4. android调用h5预加载图片,图片预加载 preload

    图片预加载 HTML5学堂:2014年年初的时候,曾经在自己的博客"独行冰海"里写过关于图片预加载和懒加载的博文,当时的文章当中没有写什么关于预载的代码范例,当前打算把预载和懒载分 ...

  5. 2020-08-23 html的标签预加载 + css的新知识 + JS的json的标准格式 + 软技能的能说会道vs安静做事

    2020-08-23 题目来源:http://www.h-camel.com/index.html [html] html的哪个标签可以预加载? 通过浏览器特性来提高资源加载速度的方法: 1.DNS ...

  6. 超详细的图片预加载和懒加载教程

    最近接手一个项目 . 结果光安装依赖都出现了一堆 麻烦 . 好不容易处理完一个 , 又来一个 .头疼啊 看到之前有一些预加载的学习笔记.于是又查查找找 ,想想写写 把预加载和懒加载的笔记写完整 发现制 ...

  7. jQuery插件之图片预加载

    背景: 图片是web页面的重要组成部分,也是前端页面优化的重要内容.当用户访问一个比较庞大的页面时,若相关资源没有提前加载,可能会展示给用户一片空白,从而导致用户流失等:再比如受网速的影响,资源加载时 ...

  8. vue实现图片预加载实操

    业务场景是这样的:在页面里有一个提醒文案,提醒文案里有个按钮,点击按钮会弹出示例弹窗,弹窗里上面有标题,中间一个比较大的图片,27kb:下面有个按钮.在苹果手机使用微信打开时,上面的标题和下面的按钮都 ...

  9. 【GoLang】《GORM实战》第三篇:关联与预加载

    文章目录 关联 分类 重写外键.引用 多态关联 外键约束 关联操作 自动添加关联 关联模式 查询关联 添加关联 替换关联 删除关联 级联删除 清空关联 关联计数 批量处理数据 预加载 Preload ...

最新文章

  1. JDK 序列化, 碰到serialVersionUID 不一致问题,怎么处理?
  2. java future用法_Java中的多线程知识点
  3. @jsonProperty 实现返回自定义属性名字
  4. BZOJ 3930 [CQOI2015]选数
  5. [转载] 陈皓:一些重要的算法
  6. C#汉字转换拼音技术详解(高性能)
  7. Linux——vim编辑器详解
  8. linux suse最新版本,SUSE Linux Enterprise Server 15 正式发布
  9. 线段树i hate it
  10. android studio 工程rebuild没反应,Andriod Studio Clear Project或Rebuild Project出错
  11. 大一python题库及答案_Python经典题库及答案-python经典编程题
  12. XCEL查找SQL SERVER数据库的数据
  13. [转]2009年河南省高考零分作文:兔子,你就是一个傻B
  14. 【HNOI2017】大佬-dalao
  15. 局域网电脑设置固定ip
  16. Spring的9处调用后置处理器
  17. 美国J1签证可以免签去哪些国家?
  18. ubuntu系统vim常用命令学习以及ubuntu软件下载安装
  19. win10下装win7双系统安装教程
  20. 用Python爬取大火的《海王》豆瓣评论

热门文章

  1. MVC北京络捷斯特第三方物流系统技术解析(九)到货通知
  2. Python 中私有变量的定义和用法
  3. 漏洞修复:HTML5: Overly Permissive CORS Policy
  4. 【Vue3】学习:常用的组合式Api
  5. IEEE终身会士、外籍院士特邀主讲,第三届消费电子与计算机工程国际学术会议截稿倒计时!
  6. SDP零信任网络安全架构
  7. TCP/IP——广播和多播(组播)
  8. 坚果云android功能,坚果云-安卓版5大新功能,一次性解锁!
  9. 《明朝那些事》爆笑妙语摘录
  10. 编写一个方法,去掉数组中重复元素