10分钟了解defer与async
2024-12-09 11:13:16

defer与async不怎么常用,而且这个涉及到<scrpit/>的基础使用方式,都是一些远古常识题,

正文

asyncdefer 是两种不同的JavaScript特性,它们的主要区别在于它们的执行顺序和时间点。

在正式进入二者的区别之前,我们需要先了解所有scprit的脚本对页面执行的阻塞的影响。

属性

async属性是 HTML5 中的新属性

  • async:可选,表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。
  • charset:可选。表示通过 src 属性指定的代码的字符集。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。
  • defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。IE7 及更早版本对嵌入脚本也支持这个属性。
  • language: 已废弃。原来用于表示编写代码使用的脚本语言(如 JavaScript 、 JavaScript1.2 或 VBScript )。大多数浏览器会忽略这个属性,因此也没有必要再用了。
  • src:可选。表示包含要执行代码的外部文件。
  • type:可选。可以看成是 language 的替代属性;表示编写代码使用的脚本语言的内容类型(也称为 MIME 类型)。虽然 text/javascript
    和 text/ecmascript 都已经不被推荐使用,但人们一直以来使用的都还是 text/javascript 。实际上,服务器在传送 JavaScript 文件时使用的
    MIME 类型通常是 application/x–javascript ,但在 type 中设置这个值却可能导致脚本被忽略。另外,在非IE浏览器中还可以使用以下值:
    application/javascript 和 application/ecmascript 。考虑到约定俗成和最大限度的浏览器兼容性,目前 type 属性的值依旧还是
    text/javascript 。不过,这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/javascript 。

引入

内联形式

这种方式指的是在 html 文件中,添加一个<script></scritp>标签,然后将 JavaScript代码直接写在里面,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>script 标签</title>
<script type="text/javascript">
console.log('内联 JavaScript');
</script>
</head>

<body>
<!-- content -->
</body>

</html>

外置形式

外置形式是将 JavaScript 代码写在外部的一个文件里面,在 html 文件中通过 <script> 标签的 src 属性引入,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>script 标签</title>
</head>

<body>
<!-- content -->
<script type="text/javascript" src="./js/01.js"></script>
</body>

</html>

两种引入形式的比较

对于这两种方式,毫无疑问,外置形式明显好于内联形式,主要表现为以下方面:

  • 可维护性:外置 Javascript 文件可以被多个页面调用而不用在每个页面上反复地书写.如果有需要改变的部分,你只需要在一处修改即可.所以外置JavaScript 导致代码工作量减少,进而使得维护手续也更加方便。
  • 可缓存:浏览器能够根据具体的设置缓存链接的所有外部 JavaScript文件。也就是说,如果有两个页面都使用同一个文件,那么这个文件只需下载一次。因此,最终结果就是能够加快页面加载的速度。
  • 关注点分离:将 JavaScript 封装在外部的.js文件遵循了关注点分离的法则.总体来说,分离 HTML,CSS 和 JavaScript 从而让我们更容易操纵他们.而且如果是多名开发者同步工作的话,这样也更方便。

因此,在今后的开发中尽量使用外置方式的形式引入JavaScript

加载顺序

如果要谈<script> 标签加载顺序问题,首先要谈的就是标签的位置。

标签的位置对于JavaScript加载顺序来说有着很重要的影响。

很早之前,有个经典面试题,<script> 标签放在html头部和尾部,是否有区别?会不会有不同执行结果?

显然,是有区别的。

标签位置

标签的位置有两种,一种是方式<head>元素里面,另外一种就是放在<body> 元素中页面内容的后面(就是页面内容结尾部分)。

<head>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>

<head>
<title>Example HTML Page</title>
<script type="text/javascript" src="example1.js"></script>
<script type="text/javascript" src="example2.js"></script>
</head>

<body>
<!-- 这里放内容 -->
</body>

</html>

这是一种比较传统的做法,目的就是把所有外部文件(包括 CSS 文件和 JavaScript 文件)的引用都放在相同的地方。

可是,在文档的<head>元素中包含所有 JavaScript 文件,意味着必须等到全部 JavaScript 代码都被下载、解析和执行完成以后,才能开始呈现页面的内容(浏览器在遇到 <body> 标签时才开始呈现内容)。

对于那些需要很多 JavaScript 代码的页面来说,这无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白。

在早年前端体系不成熟的时候,有些开发者会喜欢把<script/>标签放在<head>标签中,造成页面白屏时间极长,显得极为卡顿。

很明显,这种做法有着很明显的缺点,特别是针对于现在的移动端来说,如果超过 1s 还没有内容呈现的话将是一种很差的用户体验。

为了避免这个问题,就有了下面这种加载方式。

<body>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>

<head>
<title>Example HTML Page</title>
</head>

<body>
</body>

</html>

<!-- 这里放内容 -->
<script type="text/javascript" src="example1.js"></script>
<script type="text/javascript" src="example2.js"></script>

对于这种方式,在解析包含的 JavaScript 代码之前,页面的内容将完全呈现在浏览器中。

而用户也会因为浏览器窗口显示空白页面的时间缩短而感到打开页面的速度加快了。

延迟加载

<script>的每个属性设计来肯定都是有用的,下面我们就来说一说 defer 属性。

HTML 4.01 为 <script> 标签定义了 defer 属性。

这个属性的用途是表明脚本在执行时不会影响页面的构造。

也就是说,脚本会被延迟到整个页面都解析完毕后再运行。

因此,在<script>元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>script 标签</title>
<script defer="defer" type="text/javascript" src="./js/01.js"></script>
<script defer="defer" type="text/javascript" src="./js/02.js"></script>
</head>

<body>
<!-- content -->
<script type="text/javascript" src="./js/03.js"></script>
</body>

</html>

在这个例子中,虽然我们把 <script> 元素放在了文档的 <head> 元素中,但其中包含的脚本将延迟到浏览器遇到 </html> 标签后再执行。

HTML5 规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于 DOMContentLoaded 事件执行。

在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本。

“在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本。”

这段话是《JavaScript 高级程序设计(第三版)》中的一句话,纠结了很久。自己也尝试写了一些例子,但反馈的结果都是:如果引入的 <script>标签 都使用了 defer 属性,他们的执行顺序都是按照他们引入的顺序来的。

那么作者为什么会写上这一句话呢,个人感觉原因是:即使在 HTML5 规范中有这么一条,不一定所有的浏览器厂商都会遵照这个规定,可能某些浏览器厂商并没有实现这个规范,但支持 defer 属性,那么就会出现作者所描述的那种情况,所以为了安全起见,在开发中使用一个 defer 是非常有必要的。

还有一点需要注意的是,defer 属性只适用于外部脚本文件。

区别

async

意味着函数或代码块会被异步执行。

当浏览器遇到带有 async 属性的资源时,它会立即开始下载该资源,同时继续加载页面。

这样可以避免由于同步执行而导致的页面加载阻塞问题。

然而,由于 async 并不保证具体的执行时间,所以如果在 async 代码中修改了 DOM(文档对象模型),可能会出现错误,特别是在依赖外部资源的场景下。

defer

defer 则是一种用于推迟执行的特性,这个属性在一些页面模板中会常见,在很多项目工程化的内容中,不常见。

它允许将脚本放置到页面的最后部分,直到其他所有资源都已加载完毕。

这样确保了在执行 defer 代码之前,页面已经完全准备好,从而减少了潜在的错误风险。

特别是对于那些依赖于外部资源的第三方脚本来说,使用 defer 可以提高应用的稳定性。

总结

async 更适合于不需要等待页面完整加载即可运行的第三方脚本,因为它提供了更高的灵活性,但可能伴随着一定的执行时机的不确定性。

defer 则是为了确保页面加载完成后才执行脚本,减少因页面未加载完全而引发的错误。

结语

这里算是直接搬运了其他的内容,稍微学习了一下,这是旁枝末节的内容,如果不是面试,我确实不怎么会看。

asyncdefer的使用,算是旧时代的余声了,如有需要,按需自取。

参考

谈谈 标签以及其加载顺序问题,包含 defer & async

async与defer的区别