一、背景
浏览器构建DOM
树过程中遇到script
标签就会暂定构建,并开始下载脚本,执行脚本,之后再继续构建DOM
树。
并且相对于文档,往往脚本文件体积大,并且网络下载的耗时比解析DOM
树要大得多。
1.1 script
标签的影响
阻塞浏览器构建DOM
树(即使脚本并不会调用document.write
),用户看到白屏(严格说是script
标签后面的页面部分)时间比较长。
1.2 最佳实践
把 script
标签放到body
标签最后。
二、defer
告诉浏览器可以异步下载外部脚本,不用等待下载执行,可以继续构建DOM
树,等DOM
树构建完成后才执行脚本。
下载:后台并行下载(异步下载)
执行:DOM
构建后,但在DOMContentLoaded
事件触发前
多个defer
按照书写顺序依次串行方式执行。
defer
不会阻塞DOM
树构建,但是会阻塞DOMContentLoaded
事件触发。
三、async
告诉浏览器可以异步下载外部脚本,不用等待下载执行,可以继续构建DOM
树,并且下载完成后就可以执行了。
针对完全独立的脚本。不会依赖其他脚本,同时其他脚本也不能依赖 async
脚本。
下载:后台并行下载(异步下载)
执行:下载完就执行了。无需等待其他脚本,即先加载完的先执行。async
之间,以及和DOMContentLoaded
事件处理函数执行顺序是无序的。
注意:因为JS是单线程的如果在执行async
脚本,此时其他工作就得暂定了,比如DOM树构建(不过这种情况很少见,除非JS脚本下载很快,并且DOM很大)
四、总结
下载顺序 | 执行顺序 | DOMContentLoaded | |
---|---|---|---|
async | 异步并行下载,不阻塞DOM 树构建 | 先加载完的先执行(Load-first Order) | 跟DOMContentLoaded 无关,执行顺序不定 |
defer | 同async | DOM构建后,根据文档顺序依次串行执行 | 在DOM 树构建后,但DOMContentLoaded 事件触发前执行 |
相同点
- 都不会阻塞
DOM
树构建,都是异步下载,可以让用户先看到页面内容; - 都不可以调用
document.write
,否则浏览器有个warning:
Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
差异点
defer
和async
的唯一区别就是执行时间点不同。defer
指推迟执行(执行顺序不变)async
指异步执行(执行顺序不固定)
五、动态添加的脚本行为
5.1 通过document.createElement('script')
方式添加脚本
通过document.createElement('script')
方式创建的script
对象,其async
属性默认为true
。
let script = document.createElement('script');
console.log(script.async) // true
console.log(script.defer) // false
如果显示地设置script.async=false
,则行为同defer
,即使此时script.defer=false
。
5.2 通过document.write('<script src="xxxx"><\/script>')
添加脚本
document.write
本身的功能就是向文档流里插入内容,所以通过document.write('<script src="xxxx"><\/script>')
添加脚本的效果同直接在文档里写script
标签方式。
<script>
document.write('<script defer src="./lib/normal.long.js"><\/script>')
</script>
<!-- 等价 -->
<script defer src="./lib/normal.long.js"><\/script>
六、Issues
6.1 同时添加async
, defer
的效果呢?
- 按照
async
效果执行; - 因为
async
是HTML5才提出的,而defer
是HTML4就有了,所以defer
比async
有更好的兼容性。同时添加async
,defer
的另一个效果是如果浏览器不支持async
,则降级为defer
; - 最佳实践就是把
script
标签放到body
标签最后,防止浏览器不支持async
,也不支持defer
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。