面试官:说一下script 标签中 defer(推迟) 和 async(异步) 的区别

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 面试官:说一下script 标签中 defer(推迟) 和 async(异步) 的区别

从极迷处识迷,则到处醒;将难放怀一放,则万境宽。——《小窗幽记·集醒篇》

译:在最易令人迷惑的地方识破迷惑,那么无处不是清醒的状态;将最难以放下心怀的事放下,那么到处都是宽广的路。

开始

将Javascript插入HTML的主要方法是使用script元素,

外部引入——通过src属性指定外部JavaScript文件链接

内部嵌入——直接在HTML文档内使用<script></script>标签对引入

<script>标签包含了两个特殊的属性:defer与async,

async:属性可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本有效

defer:属性可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。在IE7及更早的版本中,对行内脚本也可以指定这个属性。

——JavaScript高级程序设计

如果没有 defer 和 async 属性,浏览器会立即加载并执行相应的外部脚本,“立即”指的是在渲染该 script 标签之下的(下面的、下文的、紧接着的、紧跟着的、后续的)文档元素之前,也就是说不等待载入后续的文档元素,读到脚本就加载并执行这样就阻塞了后续文档的加载

推迟执行脚本 defer(延迟、推迟)

HTML 4.01 为<script>元素定义了一个叫 defer 的属性。这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素上

设置 defer 属性,相当于告诉浏览器立即下载,但延迟执行。

<!DOCTYPE html>
<html>
    <head>
        <title>Example HTML Page</title>
        <script defer src="example1.js"></script>
        <script defer src="example2.js"></script>
    </head>
    <body>
        <!-- 这里是页面内容 -->
    </body>
</html>

虽然这个例子中的<script>元素包含在页面的<head>中,但它们会在浏览器解析到结束的</html>标签后才会执行。

HTML5 规范要求脚本应该按照它们出现的顺序执行,因此第一个推迟的脚本会在第二个推迟的脚本之前执行,而且两者都会在 DOMContentLoaded 事件之前执行

不过在实际当中,推迟执行的脚本不一定总会按顺序执行或者在 DOMContentLoaded事件之前执行,因此最好只包含一个这样的脚本。

如前所述,defer 属性只对外部脚本文件才有效。这是 HTML5 中明确规定的,因此支持 HTML5的浏览器会忽略行内脚本的 defer 属性。IE4~7 展示出的都是旧的行为,IE8 及更高版本则支持 HTML5定义的行为。

对 defer 属性的支持是从 IE4、Firefox 3.5、Safari 5 和 Chrome 7 开始的。其他所有浏览器则会忽略这个属性,按照通常的做法来处理脚本。考虑到这一点,还是把要推迟执行的脚本放在页面底部比较好

异步执行脚本 async(异步)

HTML5 为<script>元素定义了 async 属性。从改变脚本处理方式上看,async 属性与 defer 类似。

当然,它们两者也都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与 defer 不同的是,标记为 async 的脚本并不保证能按照它们出现的次序执行,比如:

<!DOCTYPE html>
<html>
    <head>
        <title>Example HTML Page</title>
        <script async src="example1.js"></script>
        <script async src="example2.js"></script>
    </head>
    <body>
        <!-- 这里是页面内容 -->
    </body>
</html>

在这个例子中,第二个脚本可能先于第一个脚本执行。因此,重点在于它们之间没有依赖关系。给

脚本添加 async 属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。正因为如此,异步脚本不应该在加载期间修改 DOM

异步脚本保证会在页面的 load 事件前执行,但可能会在 DOMContentLoaded之前或之后。Firefox 3.6、Safari 5 和 Chrome 7 支持异步脚本。使用 async 也会告诉页面你不会使用document.write,不过好的 Web 开发实践根本就不推荐使用这个方法(document.write)。

load和DOMContentLoaded事件

  • load

MDN的解释:load 应该仅用于检测一个完全加载的页面,当一个资源及其依赖资源已完成加载时,将触发load事件。

意思是页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件。

  • DOMContentLoaded

MDN 的解释:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。

不同属性的时间流程图

下面的图片可以看出三者之间的区别:

其中蓝色代表js脚本网络加载时间,红色代表 js 脚本执行时间,绿色代表 html 解析。

紫色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

也就是说async可能乱序执行,而defer是顺序执行,这也就决定了async比较适用于百度分析或者谷歌分析这类不依赖其他脚本的库,且defer在页面加载完成后才执行,可以在脚本中操作DOM。

从图中可以看到一个普通的<script>标签的加载和解析都是同步的,会阻塞DOM的渲染,

这也是我们经常会把<script>写在<body>底部的原因之一,

为了防止加载资源而导致的长时间的白屏,

另一个原因是js可能会进行DOM操作,所以要在DOM全部渲染完后再执行

最稳妥的办法还是把<script>写在<body>底部,没有兼容性问题,没有白屏问题,没有执行顺序问题,高枕无忧。

细究

async两种情形

async使用场景

主要是不涉及操作DOM的事件,比如使用百度、谷歌分析的脚本

情况1(在 DOMContentLoaded之前执行):HTML 还没有被解析完的时候,async脚本已经加载完了,那么 HTML停止解析,去执行脚本,脚本执行完毕后触发DOMContentLoaded事件。

情况2(在 DOMContentLoaded之后执行): HTML 解析完了之后,async脚本才加载完,然后再执行脚本,那么在HTML解析完毕、async脚本还没加载完的时候就触发DOMContentLoaded事件。

综述:

  1. 无论html是否解析完成,立即执行脚本;
  2. 无论有使用多少个async加载脚本,只要脚本下载完成,立即执行脚本。与<script>标签的顺序无关。

defer两种情形

使用场景

操作DOM的脚本,为防止元素尚未加载完成,脚本找不到元素报错。

情况1:HTML还没解析完成时,defer脚本已经加载完毕,那么defer脚本将等待HTML解析完成后再执行。defer脚本执行完毕后触发DOMContentLoaded事件。

情况2: HTML解析完成时,defer脚本还没加载完毕,那么defer脚本继续加载,加载完成后直接执行,执行完毕后触发DOMContentLoaded事件。

综述

  1. 无论js文件是否下载完成,只有html解析完毕,才可以执行脚本;
  2. 脚本执行的顺序与下载的完成时间无关,按照<script>脚本的位置,顺序执行

回答面试官

“在解析HTML文档过程中,defer和async标注的脚本都会立即下载。”

“不同的是,defer脚本即使下载完成,也会被延迟到整个页面都解析完毕后再运行,运行结束后触发DOMContentLoaded事件。”

“而async脚本下载完成后立即执行,可能会在 DOMContentLoaded之前或之后,保证在页面的 load 事件前执行。

加分——

“多个带有async属性的脚本标签之间的加载顺序是不确定的,哪个脚本先下载完成就先执行哪个。”

“- 多个带有 defer 属性的脚本标签之间的执行顺序是按照它们在文档中出现的顺序来执行的。”

完结

over,撒花✿✿ヽ(°▽°)ノ✿,喜欢的朋友点个赞,欢迎留下你的评论。

相关文章
|
2月前
|
Android开发 Kotlin
Android经典面试题之Kotlin的==和===有什么区别?
本文介绍了 Kotlin 中 `==` 和 `===` 操作符的区别:`==` 用于比较值是否相等,而 `===` 用于检查对象身份。对于基本类型,两者行为相似;对于对象引用,`==` 比较值相等性,`===` 检查引用是否指向同一实例。此外,还列举了其他常用比较操作符及其应用场景。
187 93
|
18天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
1月前
|
编译器
经典面试题:变量的声明和定义有什么区别
在编程领域,变量的“声明”与“定义”是经典面试题之一。声明告诉编译器一个变量的存在,但不分配内存,通常包含变量类型和名称;而定义则为变量分配内存空间,一个变量必须至少被定义一次。简而言之,声明是告知变量形式,定义则是实际创建变量并准备使用。
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
119 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
1月前
|
前端开发 小程序 JavaScript
面试官:px、em、rem、vw、rpx 之间有什么区别?
面试官:px、em、rem、vw、rpx 之间有什么区别?
43 0
|
1月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
24 0
|
2月前
|
Java 关系型数据库 MySQL
面试官:GROUP BY和DISTINCT有什么区别?
面试官:GROUP BY和DISTINCT有什么区别?
91 0
面试官:GROUP BY和DISTINCT有什么区别?
【多线程面试题十】、说一说notify()、notifyAll()的区别
notify()唤醒单个等待对象锁的线程,而notifyAll()唤醒所有等待该对象锁的线程,使它们进入就绪队列竞争锁。
|
3月前
|
算法 Java
【多线程面试题十八】、说一说Java中乐观锁和悲观锁的区别
这篇文章讨论了Java中的乐观锁和悲观锁的区别,其中悲观锁假设最坏情况并在访问数据时上锁,如通过`synchronized`或`Lock`接口实现;而乐观锁则在更新数据时检查是否被其他线程修改,适用于多读场景,并常通过CAS操作实现,如Java并发包`java.util.concurrent`中的类。
|
3月前
|
Java
【多线程面试题十三】、说一说synchronized与Lock的区别
这篇文章讨论了Java中`synchronized`和`Lock`接口在多线程编程中的区别,包括它们在实现、使用、锁的释放、超时设置、锁状态查询以及锁的属性等方面的不同点。