script标签中defer和async的区别

238 阅读3分钟

在现代网站中,脚本通常比HTML更“重”:下载内容更多,处理时间更长。
当浏览器加载HTML并遇到 <script>...</script> 标签,会停止构建DOM,立即去执行脚本,浏览器必须等待脚本下载,执行下载的脚本然后才能处理页面其他部分。

这会导致两个问题:
1.脚本无法看到它下方的DOM,因此无法添加处理程序。
2.如果页面顶部有一个庞大的脚本,它会阻塞页面,用户无法看到内容,直到脚本下载并执行。

<p>...content before script...</p>

<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- 下面内容不会展示 直到 long.js 下载完成 -->
<p>...content after script...</p>

有一些解决办法,比如将<script>...</script>放到页面底部,这样就不会阻塞页面渲染,并且也可以看到上面的DOM。

<body>
// 所有的 DOM 渲染都在 script 标签之上

<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
</body>

但这个方案并不是完美的解决方案,比如当HTML文档内容较多时,会有明显的延迟。
幸运的是,有两个<script>属性可以帮我们解决这个问题:deferasync

延迟

defer属性告诉浏览器不要等待脚本,继续构建DOM,脚本在“后台”加载,等到DOM构建完成后再运行。

<p>...content before script...</p>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- 下面内容会立即展示出来,不会等待上面的脚本下载执行 -->
<p>...content after script...</p>

defer永远不会阻塞页面,defer总是在DOM准备就绪时(但在DOMContentLoaded事件之前)执行。

<p>...content before script...</p>

<script>
  // DOM ready after defer! 会在 defer 脚本加载完成之后弹出
  document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
</script>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- 下面内容会立即展示出来,不会等待上面的脚本下载执行 -->
<p>...content after script...</p>

defer脚本依然会保持相对顺序执行

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>

上方是有两个defer脚本,浏览器会扫描页面中的脚本并下载它们,在上面的例子中,两个脚本是并行下载的,small.js可能会先于long.js完成下载,但执行顺序依然是long.jssmall.js

异步

async脚本会在后台加载完成后执行,DOM和其他脚本不会等待async脚本,async脚本也不会等待它们,完全独立的脚本在加载完成后运行。

<body>
    <p>...content before script...</p>

    <script>
       // DOM ready after defer! 可能先于async脚本执行,也可能在async脚本后面执行
       document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
     </script>
      

    <script async src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
    <script async src="https://javascript.info/article/script-async-defer/small.js"></script>

    <!-- 下面内容会立即展示 -->
    <p>...content after script...</p>
  </body>

上面的例子中,content after script不会受到阻塞,立即显示。DOM ready after defer!可能在async脚本之前展示也可能在之后展示,small.js可能先于long.js执行,也可能在long.js之后执行。

动态脚本

我们可以创建一个脚本并使用 javascript 动态的将脚本添加到文档中。

let script = document.createElement('script');
script.src="/article/script-async-defer/long.js";
document.body.append(script);

脚本在附加到文档后会立即加载,表现为“异步”。 如果设置script.async = false,则会按照文档顺序执行,类似于 defer

在实践中,defer用于等待整个DOM渲染结束或遵循相对执行顺序的脚本。async用于独立脚本,相对执行顺序不重要。