为何要引入script 异步加载,先看下例子:
<p>...content before script...</p>
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- This isn't visible until the script loads -->
<p>...content after script...</p>
浏览器必须等待脚本加载完毕,执行已下载的脚本,才能显示剩余的页面,这会阻塞页面。
可能你会想到这样的解决方式:
<body>
...all content is above the script...
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
</body>
但这种方式并不完美,脚本只有在 HTML 文档加载完毕才会开始下载执行,对于一个长 HTML 文档,这可能是一个明显的延迟。
因此有这么两个属性来帮助我们解决此种问题:defer,async
defer
defer不会阻塞页面
defer总会在DOM准备好时执行(但在 DOMContentLoaded 之前)
defer可以保证脚本的相对顺序(浏览器会同时下载脚本,但执行脚本会按照顺序)
注:defer只应用于外部脚本,如果 script 标签没有 src 属性,defer 将不生效。
async
async会使脚本完全独立:
async不会阻塞页面
其他脚本不会等待 async脚本,async脚本也不会等他们
同理,DOMContentLoaded 和 async脚本也不会互相等待
换言之,async脚本以“加载优先”的顺序运行。
注:async只应用于外部脚本,如果 script 标签没有 src 属性,async 将不生效。
动态脚本(Dynamic scripts)
动态地创建一个脚本,并将其添加到 document
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)
动态脚本的默认表现为 “async”,当然,这可以把 async 关掉:
script.async = false;
在此情况下,表现则为 “defer”。
总结
| order | DOMContentLoaded | |
|---|---|---|
| async | 加载优先,它们的文档顺序互不影响,谁先加载执行谁。 | 无关紧要。可以在文档尚未完全下载时加载和执行。如果脚本很小或被缓存,并且文档足够长,就会发生这种情况 |
| defer | 受文档顺序影响 | 在文档加载和解析之后执行(如果需要,它们会等待),但在 DOMContentLoaded 之前。 |
可以看到,无论是 async 还是 defer,都有其不足之处(async 会打乱脚本的执行顺序,defer 保证了执行顺序,但由于解析顺序的限制,排序靠后的库即使先完成下载,也需要等待排序靠前的脚本解析完成后才能解析,这无疑增加了整个工程的加载等待时间)。