`<script>` 标签与 defer、async 属性

news/2024/7/5 2:44:28 标签: javascript, html, script, async

script__deferasync__0"><script> 标签与 defer、async 属性

文章目录

  • `

简介

在浏览器的运行环下,我们知道要想在网页中插入脚本,我们可以使用 <script> 标签来选择引入内联脚本/外部文件脚本的方式来为网页插入 js。但是脚本的加载和执行时机其实是与 HTML 文件的解析紧密关联的,本篇将详细介绍使用 <script> 标签时关于脚本加载的 deferasync 属性之间的区别。

参考

defer和async的区别https://segmentfault.com/q/1010000000640869
DOMContentLoaded与load的区别https://segmentfault.com/a/1190000019249783
[JS]DOMContentLoaded和load的区别https://blog.csdn.net/weixin_41650504/article/details/89379947

完整示例代码

https://github.com/superfreeeee/Blog-code/tree/main/front_end/html/html_script_loading

正文

script__deferasync__31"><script> 标签的 deferasync 属性

首先我们先从宏观的角度来说明 <script> 标签的属性意义,主要差别在于 deferasync 两个属性的使用,具体分成三种情况:

script_35">1. 简单引入(不添加任何额外属性):<script>

HTML 解析的过程当中,内联脚本(脚本直接写在 <script> 标签之中),和简单引入的外部脚本都会阻塞 HTML 文件的解析,等待脚本的获取(透过网路)、执行结束之后,才会继续 HTML 脚本的解析。所以执行顺序如下:

HTML 解析 → \to html"> 发现 <script> 标签,阻塞 HTML 解析 → \to html"> (非内联脚本)从网络获取外部脚本 → \to html"> 执行 js 脚本 → \to html"> 继续 HTML 解析

script_defer_41">2. 使用 defer 属性:<script defer>

如果我们在 <script> 标签上使用了 defer 属性,那这个脚本就会被视为异步脚本,它的脚本加载将会与 HTML 解析并行,并在 HTML 完全解析完毕后执行脚本。实际的执行顺序如下:

HTML 解析 → \to html"> 发现 <script defer> 标签, 阻塞 HTML 解析 → \to html"> 异步加载外部脚本 → \to html"> 直到 HTML 解析完毕后执行 js 脚本

async_script_async_47">3. 使用 async 属性:<script async>

另一种情况我们选择使用 async 属性,与 defer 相似的是脚本的加载都是异步的,也就是与 HTML 解析并行的,差异在于 async 加载的脚本会在加载完毕后立即执行,也就是下列顺序:

HTML 解析 → \to html"> 发现 <script async> 标签, 阻塞 HTML 解析 → \to html"> 异步加载外部脚本 → \to html"> 当外部脚本加载完毕之后,立即阻塞并执行 js 脚本 → \to html"> 脚本执行完毕之后继续未完的 HTML 解析

三种使用方式小结

看完三种使用 <script> 标签的方式后,这边给出一张简图来代表三种情况下 HTML 和 js 脚本的顺序关系(这边通常指的是外部文件方式引入的脚本)

  • 脚本加载:使用 deferasync 时脚本的加载(蓝色线段)能够与 HTML 解析(绿色线段)并行,也就是异步加载脚本
  • 脚本执行:defer 脚本会在 HTML 解析完毕之后执行,而 async 则会在加载完毕之后立马阻塞 HTML 解析并开始执行

代码实践测试

光说不练,下面我们实际写几个简单的文件来检验上面解析、加载、执行的规则

script_66">简单引入:<script>

第一种情况是使用最基本的情况引入脚本,分别在 <head> 中引入内联脚本,并在 <body> 的最后引入一个外部脚本。

html"><!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>script language-html" title=javascript>javascript">
      document.addEventListener('DOMContentLoaded', () => {
        console.log('on DOMContentLoaded')
      })
      window.addEventListener('load', () => {
        console.log('on load')
      })
    </script>
  </head>
  <body>
    <h2>using script basic</h2>

    <script src="basic.js">script language-html" title=javascript>javascript"></script>
  </body>
</html>
  • basic.js
console.log('loading script using basic')
  • 加载顺序

在简单引入的情况下,我们可以看到在 main 标签下面的蓝色线段就是 HTML 解析的部分,顺序正好是:

  1. HTML 文件的加载(network 下的蓝色线段)
  2. HTML 文件的解析(main 下的蓝色线段)
  3. js 文件的加载(network 下的黄色线段)
  4. js 文件的执行(main 下的黄色线段)
  5. HTML 的解析(main 下的蓝色线段)

是的与上面所说到的情况相符合;值得一提的是,在 Timings 标签下有好几个标签(时间点):

  • L: onLoad Event
  • DCL:DOMContentLoaded Event
  • FP:First Paint
  • FCP:First Contentful Paint
  • LCP:Largest Contentful Paint

这边将不会展开说明各个指标的意义,后续作者会再其他篇章详细说明。

DOMContentLoadedload 事件

上面的指标中值得一提的是 document.DOMContentLoadedwindow.load 两个事件

  • document.DOMContentLoaded 代表的是当前 HTML 文档内容准备完毕的意思
  • window.load 代表包括 HTML 文档、外部资源/脚本都加载准备完毕的意思

还记得刚刚提过在默认(简单引入)的情况下,<script> 脚本就好像一般的 HTML 标签,会被同步的加载、执行,然后继续 HTML 文档的解析,所以三个事件的顺序就是:

js 脚本执行 → \to html"> document.DOMContentLoaded 事件 → \to html"> window.load 事件

  • 控制台输出

script_defer_141">defer 属性:<script defer>

第二种我们在脚本标签加入 defer 属性

html"><!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>script language-html" title=javascript>javascript">
      document.addEventListener('DOMContentLoaded', () => {
        console.log('on DOMContentLoaded')
      })
      window.addEventListener('load', () => {
        console.log('on load')
      })
    </script>
  </head>
  <body>
    <h2>using script with defer</h2>

    <script defer src="defer.js">script language-html" title=javascript>javascript"></script>
  </body>
</html>
  • defer.js
console.log('loading script using defer')
  • 加载顺序

我们一样可以从 network 和 main 标签的蓝黄色看出文档解析和脚本加载的前后顺序。

由于使用了 defer 属性,js 文件的加载不再阻塞 HTML 文档的解析,但是 defer 属性又指定必须在 HTML 解析完毕而 DOMContentLoaded 事件之前执行,所以 DCL 事件的出现会在 defer.js 的脚本执行完毕之后

  • 控制台输出

事件顺序为:defer.js 脚本的执行 → \to html"> document.DOMContentLoaded 事件 → \to html"> window.load 事件

async_script_async_192">async 属性:<script async>

最后一种使用了 async 属性来加载脚本

html"><!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>script language-html" title=javascript>javascript">
      document.addEventListener('DOMContentLoaded', () => {
        console.log('on DOMContentLoaded')
      })
      window.addEventListener('load', () => {
        console.log('on load')
      })
    </script>
  </head>
  <body>
    <h2>using script with async</h2>

    <script async src="async.js">script language-html" title=javascript>javascript"></script>
  </body>
</html>
console.log('loading script using async')
  • 加载顺序

前面提过 async 关键字所代表的意思是完全的异步,也就是说 async.js 被视为与 HTML 的解析完全不相关的脚本,所以我们看到在脚本执行甚至加载之前,DCL 事件早早就被触发了。

  • 控制台输出

事件顺序:document.DOMContentLoaded 事件 → \to html"> async.js 脚本的执行 → \to html"> window.load 事件

注意:load 事件指的是所有外部资源/外部脚本加载 & 执行完毕之后,所以理所当然必须在 async.js 脚本执行完毕之后

结语:到底什么时候用?

我们已经把三种情况都看了一遍,看起来好像懂了什么,最后剩下的问题就是:到底什么时候该用哪个?

决策条件(使用时机)

  • async 属性:DOM 无关的脚本

由于 async 属性会与异步加载脚本并立即执行,所以脚本看见的 DOM 结构不一定是完整的,所以这时候我们应该只在这里放入 与 DOM 无关的脚本,同时也要是 希望能及早执行 的脚本,如一些 环境兼容性的 polyfill用户行为收集/分析 等脚本

  • defer 属性:DOM 的补全

defer 属性虽然也会异步加载脚本,但是他会等到初步的 HTML 文件解析完毕之后才执行脚本,所以这时候我们可以放入一些 增强/补全 DOM 的脚本(但是要小心不一定能依赖原有的 DOM 结构),例如向 vue、react 等框架都完全透过 js 脚本来掺入页面,原有的 HTML 非常的小,我们也可以使用 defer 的方式来异步加载 bundle 后的脚本,以加快页面的渲染。

  • 默认(简单引入):DOM 相关脚本

最后就是默认的脚本引入,一些 DOM 操作或需要与 HTML 解析存在特定顺序关联的脚本就可以使用简单引用的方式。


http://www.niftyadmin.cn/n/735351.html

相关文章

linux-防火墙设置

防火墙状态 查询防火墙状态 service iptables status 停止防火墙 service iptables stop 启动防火墙 service iptables start 重启防火墙 service iptables restart 永久关闭防火墙 chkconfig iptables off 永久关闭后启动 chkconfig iptables on 关闭防火墙注意 *) 如果要关闭…

JVM汇总--jvm调优-命令篇

2019独角兽企业重金招聘Python工程师标准>>> GC的最根本原因&#xff1a;垃圾收集器的工作就是清除Java创建的对象&#xff0c;垃圾收集器需要清理的对象数量以及要执行的GC数量均取决于已创建的对象数量。因此&#xff0c;为了使你的系统在GC上表现良好&#xff0c…

论文中积累的常用表达

To our best knowledge   据我们所知 Ideally,…   理想情况下 Specifically,…   具体来说 Accordingly,…   因此 With regard to   关于, 相对于 We also enforce that the following frames in a sequence must also be considered static with regard to t…

VScode一些快捷键的简记

全屏和退出   F11 调出终端   ctrl 选择所有出现的当前选择,可进行批量修改   ctrlshiftL 触发参数提示   ctrlshiftspace

python基础—函数式编程

函数式编程 1.不可变数据 2.第一类对象 3.尾调用优化&#xff08;尾递归&#xff09;  1.高阶函数 满足两个条件任意一个为高阶函数&#xff1a; 1.函数的传入参数是一个函数名 2.函数的返回值是一个函数 #非函数式 a 1 def test():global aa 1return a test() print(a)#函数…

Http 缓存: 强缓存与协商缓存

Http 缓存: 强缓存与协商缓存 文章目录Http 缓存: 强缓存与协商缓存简介参考完整示例代码正文Http 缓存机制&#xff1a;强缓存 & 协商缓存强缓存&#xff1a;Expires & Cache-ControlHttp 1.0&#xff1a;ExpiresHttp 1.1&#xff1a;Cache-Control协商缓存&#xff1…

Docker网络基础

目录 网络bridge网络none网络host网络端口端口绑定自定义网络网络 docker提供几种网络&#xff0c;它决定容器之是以及外界和容器之间怎么样去通信。 可以通过如下方法查看docker的网络&#xff1a; docker network ls null&#xff1a;无网络&#xff0c;使用这种网络的容器会…

CVPR 2022 Paper Reading List

Best Paper —— Learning to Solve Hard Minimal Problems Best Student Paper —— EPro-PnP: Generalized End-to-End Probabilistic Perspective-n-Points for Monocular Object Pose Estimation Best Student Paper Mention —— Ref-NeRF: Structured View-Dependent A…