Why You Need Script defer and async Attribute

从动图了解如何有效使用 defer 和 async 属性加载网页中的外部脚本

发现问题

<body>
<!-- 网页内容 -->
<script src="script.js"></script>
<!-- 网页内容 -->
</body>

问题一:刚进入网页就让浏览器花许多时间下载与解析脚本:如果在网页中加入脚本的话,会导致网页的渲染需要花更长的时间去完成,这是因为浏览器在加载脚本的时候会停止渲染网页,直到加载并执行完成才会继续。网页渲染到一半被搁置,对用户来说是极差的体验。

<p>第一个元素</p>
<script>
console.log(document.querySelectorAll('p')); // NodeList [ p ]
</script>
<!-- 上面的脚本选取不到之后的 DOM 内容 -->
<p>第二个元素</p>

问题二:脚本之后渲染的 DOM 元素会选取不到:如果脚本中有选取 DOM 元素的动作,但是 DOM 元素还没有被构建出来,就会出现选取不到的情况。

解决方法

现今的浏览器都有支持 HTML 内建的 defer🔗async🔗 属性,这两个属性都可以让浏览器在加载脚本的时候不会停止渲染网页,它们的差异主要在于执行时机。下面会介绍三种方法,分别是不使用任何属性、使用 defer 属性、使用 async 属性。

解决方法一:把脚本放在底部

<body>
<h1>网页内容</h1>
<script src="script.js"></script>
</body>

这是早期常见也最简单粗暴的做法,但这样会导致需要等到整个网页渲染完才能开始加载脚本,在特长的 HTML 文件或缓慢的网络速度下容易拖慢脚本被执行的速度。

解决方法二:defer

<head>
<script src="script.js" defer></script>
</head>
<body>
<p>第一个元素</p>
<p>第二个元素</p>
</body>

defer 就是东西先下载,但晚点再执行的概念,这样做我们可以在网页的开头(通常会在<head>中)就向外抓取脚本资源,并在 DOM 渲染完毕时才执行。

解决方法三:async

<script src="script.js" async></script>

总结与补充

在大多数情况下选择任一个属性都会对网页的载入速度有所改善,但是如果脚本之间有相依性,就需要特别注意了。

defer 在 DOMContentLoaded 事件之后吗?

「DOMContentLoaded」事件表示 HTML 文件已经完全被载入并被解析。对于「defer」脚本,它可以创建/删除 DOM 元素,因此「DOMContentLoaded」事件只会在所有「defer」脚本执行完成后触发,以确保在「defer」脚本进行所有可能的 DOM 更新后,最终 DOM 树状结构已经准备好。

如果同时添加 defer 与 async 会发生什么事?

<script src="script.js" async defer></script>

两项属性同时使用的情境通常在于支持旧浏览器只支持 defer 属性的情况下,如果不支持 async,就会使用 defer,后描述的属性会被视为备用方案,当首要描述的属性不支持时才会被采用。

参考资料