前言
最近Adrian在重读第四版红宝书。第二章中2.1节讲到了script标签的8个属性,其中的'async'和'defer'是关于异步脚本的,这部分之前还比较陌生,于是来学习总结一下。
预备知识
-
浏览器解析HTML过程
-
DOMContentLoaded事件
解析HTML
-
在解析的过程中如果碰到了script标签分为三种情况(规范中定义的)
标签属性 加载是否阻塞HTML解析 解析是否阻塞HTML解析 执行时机 多个脚本 <script> ✅ ✅ 随着HTML解析的进度顺序执行 顺序执行 <script defer> ❌ ✅ HTML解析完成后,DCL事件前执行 在HTML解析完成后顺序执行 <script async> ❌ ✅ 加载完成后立即执行,Load事件之前 顺序不固定 ⚠️ script标签前面的CSS如果没有加载完,JS的执行会阻塞。因为JS和CSS都可以对样式进行修改,那么只有在保证执行顺序的情况下才可以正确渲染。
图解
-
然而在实际中,有的现代浏览器会按照优先级预加载资源,并不是解析到了script标签才去请求资源,这样做减少了资源请求的时间。在Chrome中,因为脚本和样式文件具有阻塞性(JS可能会阻塞HTML解析,CSS不会阻塞HTML解析,但是会阻塞页面渲染),所以它们的优先级更高。图片等其他二进制资源后续加载。所有的资源都是在一开始解析HTML的时候就开始加载的。
如何使用
defer
红宝书中这样说
推迟执行的脚本不一定会总会按顺序发生或者在DCL事件执行之前执行,因此最好只包含一个这样的脚本
是因为各个浏览器在优化的过程中可能会偏离规范,有的浏览器会忽略这个属性。
async
- 由于async的执行顺序不固定,所以要求脚本之间是不能相互依赖的,需要是独立的脚本。
🚫defer和async都属于异步脚本,不可以在文档中直接调用documnet.write()方法;
💡如果async和defer同时使用会优先使用async,如果浏览器不支持则会降级到defer(因为async是HTML5的特性,而 defer在HTML4中就已经存在)
总结
💡由于大部分现代浏览器会预加载资源,所以在使用时只要遵循以下原则就可以了
- CSS放在页面头部,保证它先加载完成不会阻塞JS的运行;
- JS放在页面底部,保证了兼容性的同时没有让脚本的加载阻塞到HTML的解析,减少了白屏时间。