preload 让加载和解析解耦

4,469 阅读7分钟

TL;DR

  • preload本质:preload 是声明式的 fetch,可以改变浏览器加载资源的优先级,强制浏览器请求资源,同时不阻塞文档 onload 事件,也因此可以将 load 事件与脚本解析过程解耦
  • prefetch本质:让浏览器空闲的时候加载下一页可能需要的资源,同样的load和解析解耦
  • dns-fetch,让浏览器提前做dns预解析,当静态资源和html不在同一个域的时候,特别好用
  • async,defer是script的属性,其让html解析与下载并行,但是async下载完之后立即执行,而defer是html解析完之后再执行,一般按照顺序执行,但据说不是百分百靠谱

概述

一般写html页面,script写在body结束标签前,这样只有当遇到script标签的时候,才会加载执行,费加载时间不科学吖!尽管大多数基于标记语言的资源能被浏览器的预加载器(Preloader)提前加载,但还是不尽如人意!

理想情况是,先从文档需要的资源,根据优先级,下载到本地,等到需要的时候,就可以直接使用或者解析执行!
然后!对!preload就可以干这个事!可随时关注preload的动态,就目前很多现代浏览器已经支持!

preload大白话的意思是,”嗨,浏览器!这个资源在这个页面后面会用到,现在先加载它吧。“

好处

好处远不止上面的:

  • 本质!preload 是声明式的 fetch,可以改变浏览器加载资源的优先级,强制浏览器请求资源,同时不阻塞文档 onload 事件,也因此可以将 load 事件与脚本解析过程解耦
  • 资源的提前加载。<link rel="preload" href="late_discovered_thing.js" as="script">
  • 字体的提前加载。<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
  • 动态加载,但不执行。加载某一资源但却不想立即执行它,而是想在特定的时机执行。后面细说代码。
  • 基于标记语言的异步加载。<link rel="preload" as="style" href="asyncstyle.css" onload="this.rel='stylesheet'">,加载完之后就可以应用到文档里了,script也同理,这里的onload是指window的onload事件,在加一些统计脚本的时候挺合适,不影响页面的onload,<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属性,能让浏览器加载自己需要的资源。<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,var isSupportPreload = document.createElement("link").relList && document.createElement("link").relList.supports('preload')

怎么使用preload

先说说link标签

先说下link标签的使用。

  • link规定了外部资源与当前文档的关系!
  • 于是link有不可或缺的两个属性,href外部资源的路径,rel外部资源与当前文档的关系,rel其实relation的缩写,但因为多数用于css,所以rel是stylesheet的时候,是可以省略的
  • link标签只能出现在head
  • 有全局属性和onload事件,onload事件是指文档所有资源加载完之后
  • media:这个属性规定了外部资源适用的媒体类型。它的值必须是"媒体查询"。这个属性使得用户代理能选择最适合设备运行的媒体类型。<link rel="stylesheet" media="(max-width: 800px)" href="example.css" />
  • type这个属性被用于定义链接的内容的类型。这个属性的值应该是像text/html,text/css等MIME类型。这个属性常用的用法是定义链接的样式表,最常用的值是表明了CSS的text/css。

preload是在link上的

  • 使用preload很简单,就是link的rel="preload",href是仍然是资源路径,然后加上as来说明加载的内容的类型就可以了,常用的"script" "style" "image" "media" "document"
  • preload加载字体的时候,crossorigin 属性是必须的,即便是字体资源在自家服务器上,因为用户代理必须采用匿名模式来获取字体资源。type 属性可以确保浏览器只获取自己支持的资源。比如Chrome 支持 WOFF2,看到这个就下载了,遇到不识别的类型就忽略
<!-- js -->
<link rel="preload" href="static.js" as="script" onload="preloadFinished()">
<!-- css -->
<link rel="preload" href="static.css" as="style" onload="preloadFinished()">
<!-- 字体,注意crossorigin和type的增加 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

细说,动态加载,但不执行

其实动态加载就是插入带preload的link,执行的时候,动态的创建script。

function loadScript(url){
    var link = document.createElement("link");
    link.href = url
    link.rel = "preload";
    link.as = "script";
    document.head.appendChild(link);
}
function execScript(url){
    var script = document.createElement("script");
    script.src = url;
    document.body.appendChild(script);
}
// 加载这个js,但不执行
loadScript("myscript.js")
// 某个时机执行
setTimeout(function(){
    execScript("myscript.js")
},1000)

preload的坑

注意吖,不是说加载了就是执行!!!!!
也就是preload只管加载!!!
换句话说,你买东西,preload相当于快递到你家门口了,但是你只有拆了包裹才算是使用!!!
代码的体现就是,以前的<link href="xx.css"><script src="xxx.js">还是要写在相应的位置的!
然后在head头里加上<link rel="preload" href="xx.css" as="style"><link rel="preload" href="xxx.js" as="script">,preload是锦上添花的!

浏览器加载机制

我觉得吧,先去知道浏览器加载机制之后,才能更加体会到preload的美!

这边简单的总结下,浏览器的加载机制:

  • 资源分类。html,css,js...
  • 安全策略检查。是否加载某处的资源
  • 资源优先级计算。html、css、font资源 => preload资源、script、xhr请求 => 图片、语音、视频 => prefetch预读取的资源(多用于dns)
  • 综合安全策略和优先级,开始下载资源
  • 谷歌控制台查看资源优先级的方法:打开开发者工具 -> 点击network -> 刷新页面 -> 在出现的表格的表头那行右击(就是Name,type,size...)选择Priority

prefetch与preload

prefetch相当于说,“嗨,浏览器,下个页面可能要用到这个资源,你闲着无聊就帮我加载下呗~”。 用法跟preload差不离,link的rel="prefetch"

和preload区别:

  • preload是让浏览器提前加载当前页面比较重要的资源,,prefetch是让浏览器没活的时候加载下个页面可能用到的资源
  • prefetch并没有同域的限制,但preload有
  • 都不会影响onload事件,准备说有点load事件和解析解耦的感觉
  • 当页面上使用到这个资源时候 preload 资源还没下载完,这时候不会造成二次下载,会等待第一次下载并执行脚本。prefetch差不多吧。
  • 对于 preload 来说,一旦页面关闭了,它就会立即停止 preload 获取资源,而对于 prefetch 资源,即使页面关闭,prefetch 发起的请求仍会进行不会中断。

随手说下dns-prefetch

先说域名只是服务器ip地址的美化,也就是虽然请求域名,但实际是先请求dns服务器,dns服务器返回域名所代表的真正的服务器ip,浏览器再向服务器发送请求。
dns-prefetch就是让浏览器先将域名发给dns服务器,让浏览器知道,域名所指的真正的服务器,之后在发送请求的时候,就直接发给服务器了。
简单的一行就能让支持的浏览器提前解析DNS。也就是说在浏览器请求资源时,DNS查询就已经准备好了。

使用方式<link rel="dns-prefetch" href="//example.com">

使用场景:

  • 项目的静态资源和html不在同一个域上
  • 有重定向的域名

还有async,defer

直接引用这个回答 ,当浏览器碰到 script 脚本的时候:

  • 没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。<script src="script.js"></script>
  • 有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。<script async src="script.js"></script>
  • 有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。<script defer src="myscript.js"></script>

不知道是谁的图片

preload的好处
w3c的preload,一眼可以看到支持
浏览器加载机制
prefetch和preload
一箩筐的预取技术