数字营销 · Web开发 · 基础设施

HTML5中defer和async的比较

通过实际实验分析 HTML5 中 defer 和 async 的区别,了解外部 JavaScript 的下载、执行时机,以及它们对 DOM 解析和 DOMContentLoaded 事件的影响。

在网站页面中,通常需要引入外部 JavaScript 资源,然而外部的 JS 资源可能会导致 DOM 解析阻塞,影响页面加载速度。通过异步或者延迟执行 JS,可以在一定程度上减少外部脚本对页面解析的影响。用法是在 script 标签中使用 deferasync,那么它们两个有什么区别呢?

从字面上来理解,defer 是延迟,而 async 则是异步(Asynchronous)。

W3C 对 deferasync 的解释大致可以概括为:defer 表示脚本会延迟到文档解析完成后再执行;async 则表示脚本下载完成后即可执行,不需要等待整个文档解析完成。更详细的说明可以参考 script 标签

概念的东西让人很难琢磨,下面是我对使用不同属性的外部脚本进行的一个实验。用 PHP 模拟一个外部 JavaScript 脚本:

<?php
header("Content-Type:text/javascript;charset=utf-8;");
sleep(2);
?>
console.log("This is the Script");

前台页面代码如下:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script src="./js.php" defer></script>
<!-- 对上面的引入脚本分别进行 defer 和 async 测试 -->
</head>
<body>
This is The Document.
</body>
</html>

下面两张图片分别是使用 deferasync 时的时间线:

使用defer的时间线
使用async的时间线

注意那条蓝色的时间线是 DOMContentLoaded 时间,可以看到使用 defer 时,DOMContentLoaded 事件是在 JS 脚本执行完之后发生的,而使用 async 时,则不一定要等到脚本执行完成。

为什么会这样呢?

我们知道,如果 JavaScript 引入脚本既不使用 defer 也不使用 async,在传统的脚本加载方式下,浏览器在解析到脚本时,通常需要先下载并执行脚本,然后才能继续解析后面的 HTML。

而根据前面的概念,defer 是延迟执行,async 是异步执行。它们之间最大的区别在于:defer 的执行时间与 HTML 文档解析完成有关,而 async 的执行时间是不确定的,只要脚本下载完成,就可能马上执行。

它们的共同点是,脚本下载本身不会像普通同步脚本那样直接阻塞 HTML 的继续解析。

如果一个页面中有多个使用 defer 属性的外部脚本,它们会按照在文档中的顺序依次执行。

defer和async的应用

由于 defer 会在页面解析完成后执行,所以当 JS 脚本运行时,页面的 DOM 已经基本解析完成,这时候可以直接进行 DOM 操作。

例如上面的代码中,如果把 js.php 改成如下代码:

<?php
header("Content-Type:text/javascript;charset=utf-8;");
sleep(2);
?>
document.body.innerHTML="Body Content Has Been changed";

打开前面的页面,会发现页面内容被修改。

使用 defer 时还需要注意,由于它的执行时机是在文档解析完成之后,因此 defer 脚本会参与 DOMContentLoaded 的时序。

例如:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">

<script>
document.addEventListener("DOMContentLoaded",function(){
console.log("内部脚本,DOMContentLoaded发生!");
},false);
</script>

<script src="./js.js" defer></script>
<!-- 外部脚本内容:console.log("外部脚本, 加载完毕!"); -->

</head>
<body>
This is The Document.
</body>
</html>

可以发现,defer 外部脚本会在 DOMContentLoaded 之前执行。

而对于使用 async 的外部脚本来说,则是在 JavaScript 文件被完全下载之后尽快执行,所以它可能发生在 DOM 解析完成之前,也可能发生在之后。

这里的问题就是,如果异步加载的 JS 在 DOM 准备好之前执行,那么直接操作 DOM 的代码可能无法正常工作;如果脚本在 DOMContentLoaded 之后才执行,那么依赖这个事件的逻辑也可能已经错过事件触发时机。

例如:

document.addEventListener("DOMContentLoaded",function(){
console.log("DOMContentLoaded Event");
},false);

如果这个脚本通过 async 异步加载,而且脚本本身加载时间比较长,那么当脚本真正执行时,DOMContentLoaded 事件可能已经发生,因此事件监听函数就不会被调用。

综上,deferasync 最大的区别还是执行时机:

defer:脚本异步下载,但等待 HTML 解析完成后按顺序执行。

async:脚本异步下载,下载完成后立即执行,不保证执行顺序。

两者的执行时机不同,也决定了最终的使用场景不同。实际开发中,需要根据脚本是否依赖 DOM、是否依赖其他脚本以及是否有执行顺序要求来选择合适的方式。

参考资料

  1. HTML script defer Attribute
  2. HTML script async Attribute

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。