script标签以及defer和async属性

1. <script>标签

将JavaScript代码嵌入到HTML中主要方式是使用<script>元素。
使用<script>的方式有两种:
(1)直接在网页中嵌入JavaScript代码:

<script>
  function sayHi() {
    console.log("Hi");
  }
</script>

包含在<script>中的代码会被从上到下执行,在<script>元素中的代码被计算完成之前,页面的其余内容不会被加载也不会被显示。

(2)引入外部文件中的代码,使用src属性,该属性的值是一个URL,指向包含js代码的文件。

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

与解释行内的js代码一样,在解释外部的js文件时,页面也会阻塞,阻塞的时间也包含下载文件的时间(不使用defer和async的情况下)。

如果使用了src属性,就不应该在<script></script>之间再包含其他js代码。如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。

在没有使用deferasync属性时,浏览器会按照<script>在页面中出现的顺序依次解释它们。第二个<script>元素的代码必须在第一个<script>元素的代码解释完毕才能开始解释,依次类推。

2. <script>标签位置

浏览器的渲染引擎与js引擎是互斥的,当HTML解析器遇到一个script标签时,它就会暂停渲染过程,将控制权交给js引擎。js引擎对内联的js代码会直接执行,对外部的js文件要先获取,再执行。等js引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续DOM和CSSOM的构建。

过去,所有的script标签都被放在页面的<head>标签内,如下:

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="example1.js"></script>
    <script src="example2.js"></script>
</head>

这种做法的主要目的是将外部CSS和JavaScript文件集中在一起。然后所有的JS文件都放在<head>内,意味着所有的JavaScript代码都下载、解析和解释完毕后,才能开始渲染页面(页面在浏览器解析到<body>的起始标签时开始渲染)。如果引入的脚本过多,会导致页面的渲染明显延迟,浏览器窗口完全空白。
为了解决这个问题,现在通常将所有JavaScript的引用放在<body>元素中的页面内容后面:

  <body>
    ......
    <script src="example1.js"></script>
    <script src="example2.js"></script>
  </body>

这样会在处理js代码之前完全的渲染页面,用户会感觉页面加载更快了。

image.png

image.png

可以发现,没有加defer和async属性的脚本,在遇到<script>标签时,首先开始下载脚本,从脚本开始下载到脚本完成执行,整个过程中,HTML的解析都是停止的。当脚本解析完成后,HTML解析继续。

3. <script>标签的defer属性

defer属性只对外部的脚本文件有效,给<script>标签添加defer属性,可以推迟脚本的执行。在遇到脚本时,还是会立即下载,但是会延迟执行。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="./scripts/a.js" defer></script>
  </head>
  <body>
    <ul>
      <li>HTML</li>
      <li>CSS</li>
      <li>JAVASCRIPT</li>
    </ul>
  </body>
</html>

上面的<script>标签虽然包含在<head>中,由于加上了defer属性,它们会在浏览器解析到结束的</html>标签后才会执行。
HTML5规范要求脚本应该按照它们出现的顺序执行,因此第一个推迟的脚本会在第二个推迟的脚本之前执行,而且两者都会在DOMContentLoaded事件之前执行。但是在实际情况下,推迟执行的脚本不一定总会按顺序执行,或者在DOMContentLoaded事件之前执行。因此最好只包含一个这样的脚本。

image.png

可以发现,对于defer的脚本,脚本下载时,HTML仍然在解析,当HTML解析完成以后,开始执行脚本,即延迟执行。

4. <script>标签的async属性

async属性也只适用于外部脚本,浏览器同样会立即下载脚本,但是与defer不同的是,async的脚本不能保证按照它们出现的次序执行,而且当该脚本下载完成后就会立即执行该脚本。由于脚本大小不一样,下载完成的所需要的时间不同,所以这些脚本不能保证按照出现顺序执行。

image.png

可以发现,对于async的脚本,脚本下载时,HTML仍然在解析,但是与defer不同的是,当脚本下载完成后立即就会执行脚本。

脚本下载可能很快,此时HTML还没有完成解析(DOMContentLoaded事件还没触发),首先暂停HTML解析,去执行脚本,之后继续解析。
脚本下载也可能较慢,此时HTML解析已经完成了(DOMContentLoaded事件已经触发了),直接执行脚本。

async属性的脚本会保证在页面的load事件之前执行,但是可能在DOMContentLoaded事件之前或者之后执行,由于无法确定该脚本执行时机,异步脚本不应该在加载期间操作DOM。

5. 如何使用
  1. 如果脚本是一个模块并且不依赖于其他任何脚本,则使用async
  2. 如果脚本依赖于其他脚本或者被其他脚本依赖,则使用defer
  3. 如果该脚本很小并且被一个async的脚本依赖,则使用内联的脚本,并且把该脚本放在async脚本的上面。

参考[async vs defer attributes](async vs defer attributes - Growing with the Web)
js高程四

  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值