HTML5 defer和async的区别详解
在HTML页面中插入JavaScript的主要方式是使用<script>
元素。该元素由Netscape创造,并在Netscape Navigator 2中首次实现,随后被纳入正式的HTML规范。HTML 4.01为<script>
定义了6个属性,其中包括defer
和async
。这两个属性都是可选的,并且仅对外部脚本文件有效。
一、无defer或async属性的<script>
标签
当浏览器解析到如下无defer
或async
属性的<script>
标签时:
<script src="main.js"></script>
浏览器会立即加载并执行指定的脚本。这里的“立即”指的是在渲染该<script>
标签之下的文档元素之前进行。也就是说,浏览器不会等待后续载入的文档元素,一旦读到该<script>
标签就会立即加载并执行脚本。
二、有async属性的<script>
标签
当浏览器解析到带有async
属性的<script>
标签时:
<script async src="main.js"></script>
浏览器会立即开始下载脚本,但这不会妨碍页面中的其他操作,例如下载其他资源或等待加载其他脚本。加载和渲染后续文档元素的过程会与main.js
的加载与执行并行进行,即异步执行。
需要注意的是,async
属性不保证脚本会按照其在HTML中出现的先后顺序执行。因此,确保各个脚本之间互不依赖非常重要。指定async
属性的目的是避免页面等待脚本的下载和执行,从而能够异步加载页面的其他内容。建议异步脚本不要在加载期间修改DOM。
异步脚本一定会在页面的load
事件触发前执行,但可能会在DOMContentLoaded
事件触发之前或之后执行。支持异步脚本的浏览器有Firefox 3.6、Safari 5和Chrome。
三、有defer属性的<script>
标签
当浏览器解析到带有defer
属性的<script>
标签时:
<script defer src="main1.js"></script>
<script defer src="main2.js"></script>
这表示脚本会被延迟到文档完全被解析和显示之后再执行。加载后续文档元素的过程将和脚本的加载并行进行,即异步执行。HTML5规范要求脚本按照它们在HTML中出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,并且这两个脚本会先于DOMContentLoaded
事件执行。
然而在实际情况中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded
事件触发前执行。所以,为了避免潜在的问题,最好只包含一个延迟脚本。
IE 4 - IE 7还支持对嵌入脚本使用defer
属性,但IE 8及之后的版本则完全支持HTML5规定的行为。最早支持defer
属性的浏览器有IE 4、Firefox 3.5、Safari 5和Chrome。其他不支持该属性的浏览器会忽略这个属性,像平常一样处理脚本。因此,把延迟脚本放在页面底部仍然是最佳选择。
四、defer和async的对比
为了更直观地理解二者的区别,我们假设蓝色线代表网络读取(脚本下载),红色线代表执行时间,绿色线代表HTML解析。从这个角度来看,我们可以总结出以下要点:
- 网络读取(下载)方面:
defer
和async
在网络读取(下载)阶段是一样的,相较于HTML解析,它们都是异步的。 - 脚本执行时机:二者的差别在于脚本下载完成之后何时执行。显然,
defer
更接近我们对于应用脚本加载和执行的常规要求。defer
会按照脚本在HTML中出现的顺序执行,我们可以利用这一特性来确保脚本的执行顺序符合我们的预期。 - 执行顺序:
async
则是乱序执行的。对于async
来说,脚本的加载和执行是紧密相连的,只要脚本加载完成就会立即执行,而不考虑脚本在HTML中声明的顺序。 - 使用场景:仔细思考会发现,
async
对于应用脚本的用处不大,因为它完全不考虑脚本之间的依赖关系(哪怕是最低级的顺序执行)。不过,它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说是非常合适的,最典型的例子就是Google Analytics脚本。
综上所述,defer
和async
的使用需要根据脚本的具体需求和依赖关系来决定,合理使用它们可以优化页面的加载性能。