纸上得来终觉浅,绝知此事要躬行。
最近复习了下浏览器的加载过程,看到了关于资源文件的加载顺序。MDN 的浏览器的工作原理 里面说到 对于<script>标签(特别是没有 async 或者 defer 属性)会阻塞渲染并停止HTML的解析
,说明有async、defer
属性的script
标签不会阻塞 html 的解析。具体加载顺序是什么样的呢,加了之后的加载顺序又是怎样的,同时有两个属性的加载顺序又是什么呢,我感觉很有必要实践一下。
MDN 上有说明如下
async 和 defer 属性值为 bool,它用来说明script脚本应该如何执行。在没有 src 属性的情况下,async 和 defer 属性可以不指定值。使用该属性有三种模式可供选择,如果async 属性存在,脚本将异步执行,只要它是可用的,如果 async 属性不存在,而 defer属性存在,脚本将会在页面完成解析后执行,如果都不存在,那么脚本会在 useragent 解析页面之前被取出并立刻执行。
预加载扫描器
引用自 MDN
浏览器构建DOM树时,这个过程占用了主线程。当这种情况发生时,预加载扫描仪将解析可用的内容并请求高优先级资源,如CSS、JavaScript和web字体。多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用来请求它。
我们可以写一段代码来证明一般的同步 script 也是有预加载的。
<script>
for (let i = 0; i < 10000000000; i++) {
}
console.log('html long time used', new Date().getTime())
</script>
<!-- defer 文件很大,但是全部都是注释 -->
<script type="text/javascript" src="./defer.js"></script>
刷新查看结果可以看到两个时间间隔仅有 19ms,但是 network
里面可以看到下载 defer.js 文件用了 3 秒,说明这个文件是有被提前下载的,而不是解析到 script
标签才开始下载。
async
当外部资源文件有 async 属性时,脚本是异步执行。测试一下。在 async 脚本后面加上一个加载时间很长的 js 文件。
<script type="text/javascript" async src="./async.js"></script>
<script type="text/javascript" src="./load-long.js"></script>
<script>
console.log('load long')
</script>
可以看到 html 解析完成前,async 脚本就已经执行了。
defer
对于 defer 属性的脚本,需要等到页面解析完成后才执行。这时我们在 async 的脚本前加上 defer 脚本,即可测试 defer 的执行时间。
<script type="text/javascript" defer src="./defer.js"></script>
<script type="text/javascript" async src="./async.js"></script>
<script type="text/javascript" src="./load-long.js"></script>
<script>
console.log('load long')
</script>
可以看到 defer 文件的执行是等到当前文件解析完成后才执行。
preload
前面 MDN 里面说预加载扫描器
会对字体进行预加载,一般我们使用字体文件是在样式中通过 @font-face
导入,这时候字体文件是不能预加载的。
这时我们通过 link
标签提前导入字体文件。需要添加 rel='preload' 和 as='font' 两个属性。添加了这两个属性后可以看到虽然字体文件预加载了,但是在
css 中字体文件还会重复加载。
<link href="./Ranchers-Regular.ttf" type="font/ttf" rel="preload" as="font">
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/34b3387edec04f118c28121c9f97bab7~tplv-k3u1fbpfcp-watermark.image)
原来还需要添加 crossorigin
属性,只有字体文件需要添加这个属性。
如果你已经有了一个可以正确工作的CORS设置,那么你也可以同样成功的预加载那些跨域资源,只需要你在元素中设置好crossorigin属性即可。一个有趣的情况是,如果你需要获取的是字体文件,那么即使是非跨域的情况下,也需要应用这一属性。因为各种各样的原因,这些获取请求必须使用以匿名模式使用CORS(如果你对其中的细节感兴趣,可以查看Font fetching requirements一文)。
<link href="./Ranchers-Regular.ttf" type="font/ttf" rel="preload" as="font" crossorigin="anonymous">
此时可以看到字体文件已经预加载而且也不会重复加载。
也可以看这个例子
另外 preload
只是会提前下载,并不会执行。所以该使用的地方照常使用,但是不会重复下载了。如果你提前下载了没有使用,console
里面会有警告提示没有用的 preload
prefetch
rel 还有一种属性类型 prefetch
也可以提前进行资源文件的下载。preload
需要配合 as
才会进行预加载,但是 prefetch
不需要。
可以看下例子:
<link rel="prefetch" href="prefetch_1.js"></script>
<link rel="prefetch" href="prefetch_2.js"></script>
prefetch_1.js
console.log('this is prefetch 1 file');
可以看到 network 里面有下载这两个 js 文件,你在这两个 js 中打日志,会发现并没有执行。也就是可以提前下载,再次使用 script
标签使用的时候就不会重复下载。
当我们在 html
里面加入 script
引用 prefetch_1.js
之后,在看下 network并没有变化,但是 console
里面有日志了。
<link rel="prefetch" href="prefetch_1.js"></script>
<link rel="prefetch" href="prefetch_2.js"></script>
<script type="text/javascript" src="./prefetch_1.js"></script>
总结
- 对部分资源文件浏览器会预加载但是不会执行
- 没有
async/defer
属性的 js 会阻塞 html 解析 async
属性的 js 会异步加载defer
属性的 js 会等待 html 解析完成在执行- 对于浏览器没有预加载的资源可以通过
rel='preload' 和 as='xxx'
或者rel='prefetch'
强制预加载,字体文件需要添加crossorigin="anonymous"
。注意rel='preload'
或者rel='prefetch'
只是下载了文件,并没有执行。另外因为提前下载会消耗 TCP 连接数,请考虑同时连接数,不能所有资源文件全部提前下载,只考虑必要的资源。