Script放在body中间会阻塞吗?defer与async的区别?

默认script标签是同步执行,因此会发生阻塞

浏览器解析html文件时,从上向下解析,解析到DOM中的script时会暂停DOM构建,在脚本加载并执行完毕后才会继续向下解析

因此可以看到,JS脚本存在会阻塞DOM解析的问题进而影响页面渲染速度


实验

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<script src="index.js"></script>
<h1>222</h1>
</body>
</html>

index.js

const startTime = new Date().getTime()
let endTime = ''

do {
  endTime = new Date().getTime()
} while (endTime - startTime <= 2000)

在浏览器中打开可以看到,script 之上的dom渲染完成,在加载和运行 script 时耗费了 2s后,script 后面的的 dom 才加载

在这里插入图片描述


解决方案

1. script 放在 body 最下方

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<h1>222</h1>
<!-- script 放在 body 最下方 -->
<script src="index.js"></script>
</body>
</html>

script 放在 body 最下方,所有的dom加载完成后才加载、执行script,因此不会阻塞页面的渲染,如下图
在这里插入图片描述


2. 使用 async 异步加载 script

dom解析时,遇到设置了async的脚本,就会在后台进行下载,但是并不会阻止dom的渲染。

当页面解析并且渲染完毕后,在Load 事件触发前执行,async脚本的加载不计入DOMContentLoaded事件统计

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<!-- 使用 async 异步加载 script -->
<script async src="index.js"></script>
<h1>222</h1>
</body>
</html>

index.js

const startTime = new Date().getTime()
let endTime = ''

do {
  endTime = new Date().getTime()
} while (endTime - startTime <= 2000)

debugger

下图可以看到,在执行断点前,dom已经渲染完毕,不会阻塞页面

在这里插入图片描述


3. 使用 defer 异步加载 script

dom解析时,遇到设置了defer的脚本,就会在后台进行下载,但是并不会阻止dom的渲染,当页面解析并且渲染完毕后。

会等到所有的defer脚本加载完毕并按照顺序执行,执行完毕后会触发DOMContentLoaded事件。

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<!-- 使用 defer 异步加载 script -->
<script defer src="index.js"></script>
<h1>222</h1>
</body>
</html>

下图可以看到,在执行断点前,dom已经渲染完毕,也不会阻塞页面
在这里插入图片描述


4. WebWorker

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<!-- WebWorker -->
<script>
  const worker  = new Worker('./index.js')
  worker.postMessage("兄弟,帮我运行下这个脚本")
  worker.onmessage = (e) => {
    console.log(e);
  }
</script>
<h1>222</h1>
</body>
</html>

index.js

const startTime = new Date().getTime()
let endTime = ''

do {
  endTime = new Date().getTime()
} while (endTime - startTime <= 2000)

onmessage = (e) => {
  console.log(e);

  postMessage("好的大兄弟")
}

下图可以看到,在执行断点前,dom已经渲染完毕,不会阻塞页面
在这里插入图片描述

在这里插入图片描述

总结

问题

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

上面代码中,<script>标签打开deferasync属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。

deferasync的区别是:defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。


解决方案

  1. 将script 放在 body 最下方
  2. 使用 async 异步加载 script
  3. 使用 defer 异步加载 script
  4. WebWorker

async、defer 推荐应用场景

  • 如果你的JS代码依赖于页面中的DOM元素,或者被其他脚本文件依赖,应当使用 defer

  • 如果你的脚本并不关心页面中的DOM元素,并且也不会产生其他脚本需要的数据,可以使用 async

  • 如果不太确定的话,选择defer 会比 async 更靠谱

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__畫戟__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值