script 的三种加载模式:默认加载、defer、async

简介: 前端西瓜哥

大家好,我是前端西瓜哥。今天我们来了解一下 script 脚本的三种加载方式。

默认加载

一般的 script 写法为:

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

这种写法有一个问题:它会 阻塞 HTML 的 DOM 构建

假如我们在 head 元素中使用了 script 脚本,它就会阻止后面元素的渲染,包括 body 元素,此时执行document.querySeletor('body')  拿到的是 null。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script>
    // 拿到 null
   console.log(document.querySeletor('body'));
  </script>
</head>
<body></body>
</html>

此外,当脚本足够大,加载执行足够久时,会导致页面长时间没能渲染出完整页面。

这也是我们将业务代码脚本放到 body 最下边的原因,这样能确保脚本能够访问一个完整的 DOM 树,也不会阻止页面的渲染。

缺点是,HTML 很长的时候,解析到脚本就会花上一点时间,然后才会请求对应的脚本资源。

不过通常来说,HTML 内容都比较简单,二者感受不到太大区别,除非你网很卡。

defer 加载

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

defer,“延迟” 之意。这里的延迟,指的是延迟执行脚本,下载则不会被阻塞。

需要注意的是, defer 属性对内嵌脚本无效。毕竟脚本内容就在 HTML 里了,完全不需要请求资源了好吧。

给 script 标签添加了 defer 属性后,脚本不会阻塞 DOM 树的构建,会先下载资源,然后等待到在 DOMContentLoaded 事件前执行。

DOMContentLoaded 事件的触发时机为初始 HTML 被构建完成时,此时 CSS、图片等资源不需要加载完,但我们的脚本要执行完。

如果多个 script 设置了 defer 属性,这几个 script 的执行顺序和声明顺序相同,即最前面的脚本先执行。并不是谁先下载谁先执行。

实际开发中,我们可以将业务代码脚本加上 defer 属性,放到更上层的 head 标签下。

这也是最新版 HtmlWebpackPlugin 插件的默认引入打包脚本的方式。

async 加载

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

async,“异步” 之意。同样对内嵌脚本无效。

设置 async  后,脚本一旦被下载好了就会执行,不管什么时机。

适合与执行顺序无关的脚本,比如广告、网站流量分析脚本。

比如插入 Google 分析脚本:

<script async src="//www.google-analytics.com/analytics.js"></script>

动态加载

还有一种用脚本加载脚本的特殊情况,这里也说一说。

<script>
  const script = document.createElement('script');
  script.src = 'app-a.js';
  document.body.appendChild(script);
</script>

脚本里创建一个 script 元素,设置好 src,然后加载到 DOM 树上,接着脚本就会下载和执行了。

创建的 script 元素默认会给 async 设置为 true,即一旦下载好就立即执行。

如果你要加载有依赖关系的多个脚本,就需要将 async 设置为 false。

<script>
  const script = document.createElement('script');
  // 取消 async 加载方式
  script.async = false;
  script.src = 'app-a.js';
  document.body.appendChild(script);
  const script2 = document.createElement('script');
  script2.async = false;
  script2.src = 'app-b.js';
  document.body.appendChild(script2);
</script>
<script>console.log('我还是所有脚本中最先执行的')</script>

这样写,就能保证先执行 app-a.js,再执行 app-b.js

它无法做到比 HTML 中其他非动态加载的 script 脚本更早执行,这点需要注意。

结尾

script 有三种常见加载模式:

  • 默认加载:会阻塞 DOM 构建
  • defer 加载:下载照旧,但执行延后
  • async:下载完就立即执行,适合没有依赖的脚本

此外还有动态加载的脚本的情况,这种脚本默认为 async 加载形式,可通过将 async 属性设置为 false 来解除,让脚本顺序执行。

我是前端西瓜哥,欢迎关注我。

相关文章
|
JavaScript
js节点、属性操作,计时器,location、history对象,常见键盘事件
js节点、属性操作,计时器,location、history对象,常见键盘事件
|
1月前
|
监控 JavaScript 前端开发
确定使用 `defer` 属性还是 `async` 属性来异步加载 JavaScript
【10月更文挑战第24天】选择使用 `defer` 属性还是 `async` 属性来异步加载 JavaScript 是一个需要综合考虑多个因素的决策。需要根据脚本之间的依赖关系、页面加载性能要求、脚本的功能和重要性等因素来进行权衡。在实际应用中,需要通过测试和验证来确定最适合的加载方式,以提供更好的用户体验和页面性能。
|
4月前
|
存储 JavaScript 索引
js arguments 的使用与自执行函数
js arguments 的使用与自执行函数
31 0
|
7月前
|
JavaScript 前端开发 Go
理解 <script> 标签的 defer 和 async 属性
理解 <script> 标签的 defer 和 async 属性
163 3
|
移动开发 Go UED
<script> 标签上的 defer 和 async 属性是什么?
它会执行以下步骤 1.暂停 HTML 文档解析器 2.创建新请求以下载脚本 3.在脚本完全下载后立即执行脚本 4.执行结束后,继续解析 HTML 文档
|
JavaScript 前端开发 UED
关于js暂停执行的方法
关于js暂停执行的方法
927 0
关于js暂停执行的方法
|
JavaScript
关于jQuery中toggle参数callback函数提前执行问题
关于jQuery中toggle参数callback函数提前执行问题
101 0
Auto.js 特殊定位控件方法 不能在ui线程执行阻塞操作,请使用setTimeout代替
Auto.js 特殊定位控件方法 不能在ui线程执行阻塞操作,请使用setTimeout代替
1346 0
Auto.js 特殊定位控件方法  不能在ui线程执行阻塞操作,请使用setTimeout代替
|
JavaScript 前端开发 API
Js 的事件循环(Event Loop)机制以及实例讲解
前言 大家都知道js是单线程的脚本语言,在同一时间,只能做同一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理等行为,防止主线程阻塞,Event Loop方案应运而生... 个人博客了解一下:obkoro1.com 为什么js是单线程? js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。 在js高程中举过一个栗子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级? 为了避免这种问题,js必须是一门单线程语言,并且在未来这个特点也不会改变。
193 0
Js 的事件循环(Event Loop)机制以及实例讲解