前言
preload和prefetch是现代浏览器对<link>标签新增的rel值,用来加快页面资源(通常是css和js)的加载速度,改善用户体验。比如<link href="./2.js" rel="preload" as="script" />
。
网络上对于preload和prefetch的讲解其实很多,看起来也很专业,但是看完了还是一头雾水。这次我来讲解一下。
与<link>常见用法的区别
之前常见的<link>是引用css,比如<link rel="stylesheet" href="https://xxx/ooo.css" type="text/css">
,现在rel值由stylesheet换成了preload或prefetch,会有什么区别呢?
-
<link>在常用用法里无法加载js,只能加载css,但是如果使用prefetch和preload,就可以加载js。
-
无论是preload还是prefetch,都是只下载资源,不执行资源,可以简单测试一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="./1.css" rel="preload" as="style" />
<link href="./2.js" rel="preload" as="script" />
<link href="./1.css" rel="prefetch" as="style" />
<link href="./2.js" rel="prefetch" as="script" />
<title>Document</title>
</head>
<body>
<div class="xx">ABCDEFG</div>
</body>
</html>
另准备css文件:.xx{color: red;}
和js文件:alert(123)
。在服务器或本地环境打开html,你会发现,尽管浏览器下载了文件,但是并没有执行。
- prefetch和preload不影响window.onload事件
也就是说,window.onload事件并不会去管prefetch和preload的资源有没有下载完毕,就当prefetch和preload的资源不存在。
prefetch和preload实际范例
比如我创建一个Vue CLI项目,一字不改,直接build项目,index.html会有这种代码:
<link href="/js/about.90b284f2.js" rel="prefetch" />
<link href="/css/app.b00e02bd.css" rel="preload" as="style" />
<link href="/js/app.c5cc92e4.js" rel="preload" as="script" />
<link href="/js/chunk-vendors.c161a506.js" rel="preload" as="script" />
<link href="/css/app.b00e02bd.css" rel="stylesheet" />
其中前4行都是预加载的资源,只下载不执行,只有第5行是正常引入css并执行。而且有一个现象是,第2行和第5行的css资源是一样的,区别是第2行是preload。为什么这样写?下文我介绍。
preload
写法
一个典型的preload是这样写的:
<link rel="preload" as="script" href="./2.js" crossorigin="anonymous" onload="handleOnload()" onerror="handlepreloadError()">
- rel属性值就是preload。
- as属性用于规定资源的类型,浏览器据此设置请求头里的Accept字段,以便能够使用正常的策略去请求对应的资源;as的值可以取style、script、image、font、fetch、document、audio、video等;如果忽略as属性,或者错误的as属性会使preload等同于XHR请求,浏览器不知道加载的是什么,因此会赋予此类资源非常低的加载优先级,所以一般不应省略as属性。
- href不多说。
- crossorigin属性用于请求跨域资源,不加可能会导致资源的二次加载(尤其是font资源)。
- onload和onerror则分别是资源加载成功和失败后的回调函数,如今网络环境下,通常不用考虑。
网上的错误介绍
有文章介绍是:
Chrome有四种缓存:http cache、memory cache、Service Worker cache和Push cache。在preload或prefetch的资源加载时,两者均存储在http cache。
说法有问题。http cache是总概念,包括disk cache和其他3个缓存。preload或prefetch存储其实是在disk cache,也就是硬盘缓存。
优点
preload可以强行修改资源加载顺序。这是什么意思?先看一张图:
这个表格来自于国外网站,从左到右是最高优先级、高优先级、中、低、最低优先级,这不是硬性标准,各大浏览器的实现不一定一致,但大致是这样。
优先级这个表格也有让人费解的地方,不过无所谓,我们现在只需要知道,想让资源不按照书写顺序加载,想让某些资源插队加载,就可以用preload。
比如现在,正常资源3.css写在第一位,但是我想让其他资源插队,早于3.css,怎么办?给其他资源设preload即可。
<link href="./3.css" rel="stylesheet" />
<link href="./1.css" rel="preload" as="style" />
<link href="./2.js" rel="preload" as="script" />
<link href="./1.css" rel="prefetch" as="style" />
<link href="./2.js" rel="prefetch" as="script" />
实际下载顺序:
image.png使用场景
- 我们依然看上文说的Vue项目,事实上在<body>里还有些代码:
<div id="app"></div>
<script src="/js/chunk-vendors.c161a506.js"></script>
<script src="/js/app.c5cc92e4.js"></script>
这两个资源在<head>里面都曾preload,这样做的好处:提早加载、DOM尾部立即执行。如果没有preload,那么在DOM加载完、js下载完成前会有一瞬间的界面垮掉。
同时你可以发现,app.b00e02bd.css在<head>里出现了2次,这样做是必须的吗?是必须的。因为preload的js会早于正常的css,无论书写顺序,所以为了保证css一定在js之前尽早下载,那么css也必须使用preload。
- 介绍一种preload之后立即执行css资源的办法
<link href="./1.css" rel="preload" as="style" onload="this.rel=stylesheet" />
这样的话,这个css会当场执行,可以省略下方那行正常引用的代码。
- 是不是所有资源都应无脑preload?
肯定不是,在刚进入网站/应用的时候,preload是有必要的,但是后续的页面跳转,就不再需要preload,而是使用另一个rel属性:prefetch。
prefetch
写法
上面有介绍:<link href="/js/about.90b284f2.js" rel="prefetch" />
网上的错误介绍
有文章对于prefetch的介绍是:
prefetch是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。
这话说的就很莫名其妙,浏览器毕竟不是人工智能,所谓的“不一定”,是个什么衡量标准呢?事实上,即便网页被关闭,浏览器对该资源的下载也不会中止,所以,浏览器是一定会加载该资源的。
prefetch到底有什么作用?
它用于加载“未来需要的”资源,而且只有5分钟的生命期,也就是说,浏览器会低优先级下载该资源,拿到资源之后会存入disk cache,未来真的需要它的时候,会从disk cache中读取,以加快页面资源加载速度。
虽然Vue把一个prefetch的资源(也就是about
开头的那个js)写在了第一位,但是可以看到,加载它的时候是最后一位(不考虑图片):
当我点击Vue项目的“About”按钮,进入About路由之后,又会请求一次这个资源,这次浏览器会从disk cache拿资源:
2.png如果页面切换足够快,会不会重复获取资源?
并不会,这是浏览器起码的优化机制。
低优先级是多低?
还是看上面那个表格,看最右侧一列的最下一个单元格,正是prefetch,说明优先级非常之低。
prefetch为什么比logo文件靠前加载?原因我不清楚,毕竟这个图只是民间总结的,而且各个浏览器的实现也不一定相同。
不要对同一个资源使用preload和prefetch
会造成2遍下载。
使用场景
用户极可能继续访问的页面需要的资源,都应该prefetch。所谓“极有可能”需要你根据各种统计数据来猜测,又或者页面是一个列表页,那么用户只有一个选择:点击进入详情页,这时候详情页需要的资源肯定要prefetch。
dns-prefetch
dns-prefetch又是什么?它跟上面两个做的事情不太一样,实际上是强迫浏览器对其他域名(通常就是DNS域名)进行提前解析,以便加快DNS域名的解析速度。
比如你的网站域名叫www.wangbadan.com
,你用的很多资源来自于cdn.jsdelivr.net
,那么你应该在index.html里最顶部位置写上:
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
会有一点效果。
defer和async
defer和async都是用在<script>标签上的属性,可以合起来讲解。
无属性 | defer | async | |
---|---|---|---|
是否阻塞HTML解析 | 下载和执行都阻塞 | 都不阻塞 | 下载不阻塞,仅在执行时阻塞 |
按书写顺序下载 | 是 | 否,跟之后的js并行下载 | 否,跟之后的js并行下载 |
下载后立即执行 | 是 | 否,在HTML全部解析后、DOMContentLoaded执行前的节点执行 | 是 |
按书写顺序执行 | 是 | 如果后续js没有“无属性”和“async”,则是,否则不是 | 先下载完的先执行,所以一定是乱序 |
按下载顺序执行 | 是 | 如果后续js没有“无属性”和“async”,则是,否则不是 | 是,但是下载是乱序的,所以总体乱序 |
使用场景
在如今2021年的前端开发中,事实上defer和async并没有什么使用场景。说的直白点,它们全过时了。为什么?
-
上方写的规则其实是靠不住的,因为毕竟依赖浏览器的实现,而实现不完全根据上方写的规则来执行。
-
如今前端开发都是脚手架开发,按需加载,由js控制加载顺序才是最可靠的。
-
defer的初衷是让你可以把js写在
<head>
里,而无需担心无法操作DOM。但是,一贯的最佳实践都是把js写在</body>
之前,所以不存在这个问题。 -
async虽然下载时不阻塞HTML解析,但是引来了很大的问题就是无法掌握执行时机,async在早年前端开发足够简单的时候是有用的,如今开发是各种依赖包,必须掌握执行时机,所以async根本无法考虑。
-
有人说async可以用在完全无依赖的js上,比如Google分析,但事实上永远把Google分析放在最后一位就能解决问题。
网友评论