你不知道的 script 标签的 defer 与 async 属性

我持续组织了近一年的源码共读活动,感兴趣的可以 点此扫码加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信进群。


fa1873dbb5badcd595dcf274f00e9124.png

前言

在面试的时候,经常会遇到一道经典的面试题:

如何优化网页加载速度?

常规的回答中总会有一条:

把 css 文件放在页面顶部,把 js 文件放在页面底部。

那么,为什么要把 js 文件放在页面的最底部呢?

我们先来看下这段代码:

<!DOCTYPE html>
<html lang="zh"><head><title>Hi</title><script>console.log("Howdy ~");</script><script src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script><script src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script></head><body>Hello 👋🏻 ~</body>
</html>

他的执行顺序是:

  • 在控制台打印:Howdy ~

  • 请求并执行 vue.global.js

  • 请求并执行 vue-router.global.js

  • 在页面中展示:Hello 👋🏻 ~

  • 触发 DOMContentLoaded[1] 事件

cadebcbd18226ef32c82c2a5c03ad1ae.png
script 加载逻辑

浏览器的解析规则是:如果遇到 script 标签,则暂停构建 DOM,转而开始执行 script 标签,如果是外部 script[2],那么浏览器还需要一直等待其「下载」并「执行」后,再继续解析后面的 HTML。

如果请求并执行「vue.global.js」需要 3 秒,「vue-router.global.js」需要 2 秒,那么页面中的 Hello 👋🏻 ~,则至少需要 5 秒以上才会展示出来。

可以看到,script 标签会阻塞浏览器解析 HTML,如果把 script 都放在 head 中,在网络不佳的情况下,就会导致页面长期处于白屏状态。

在很久以前,一般都是将这些外联脚本,放在 body 标签的最后面,确保先解析展示 body  中的内容,然后再一个个请求执行这些外联脚本。

那有没有其他更优雅的解决方案呢?

答案是肯定的,现在 script  标签新增了 2 个属性:deferasync,就是为了解决此类问题,提升页面性能的。

<script defer>

先看一下 MDN 上的解释:

这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。

有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。

文档是直接总结了他的特性,我们先看看下面的代码,展开说说细节,加深一下理解。

<!DOCTYPE html>
<html lang="zh"><head><title>Hi</title><script>console.log("Howdy ~");</script><script defer src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script><script defer src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script></head><body>Hello 👋🏻 ~</body>
</html>

他的执行顺序是:

  • 在控制台打印:Howdy ~

  • 在页面中展示:Hello 👋🏻 ~

  • 请求并执行 vue.global.js

  • 请求并执行 vue-router.global.js

  • 触发 DOMContentLoaded[3] 事件

a27701709cdec0e92f097c0d21305d1b.png
script defer 加载逻辑

如果在 script 标签上设置了 defer 属性,那么在浏览器解析到这里时,会默默的在后台开始下载此脚本,并继续解析后面的 HTML,并不会阻塞解析操作。

等到 HTML 解析完成之后,浏览器会立即执行后台下载的脚本,脚本执行完成之后,才会触发 DOMContentLoaded 事件。

看起来还是蛮好理解的吧?咱们再来讨论 2 个小细节:

Q1: 如果 HTML 解析完成之后,设置了 defer 属性的脚本还没下载完成,会怎样?

A1: 浏览器会等脚本下载完成之后,再执行此脚本,执行完成之后,再触发 DOMContentLoaded 事件。

Q2: 如果有多个设置了 defer 属性的脚本,那浏览器会如何处理?

A2: 浏览器会并行的在后台下载这些脚本,等 HTML 解析完成,并且所有脚本下载完成之后,再按照他们在 HTML 中出现的相对顺序执行,等所有脚本执行完成之后,再触发 DOMContentLoaded 事件。

最佳实践:

建议所有的外联脚本都默认设置此属性,因为他不会阻塞 HTML 解析,可以并行下载 JavaScript 资源,还可以按照他们在 HTML 中的相对顺序执行,确保有依赖关系的脚本运行时,不会缺少依赖。

在 SPA 的应用中,可以考虑把所有的 script 标签加上 defer 属性,并且放到 body 的最后面。在现代浏览器中,可以并行下载提升速度,也可以确保在老浏览器中,不阻塞浏览器解析 HTML,起到降级的作用。

注意:

  • defer 属性仅适用于外部脚本,如果 script 脚本没有 src,则会忽略 defer 特性。

  • defer 属性对模块脚本(script type='module'[4])无效,因为模块脚本就是以 defer 的形式加载的。

<script async>

按照惯例,先看一下 MDN 上的解释:

对于普通脚本,如果存在 async 属性,那么普通脚本会被并行请求,并尽快解析和执行。

对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。

该属性能够消除解析阻塞的 Javascript。

解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后才能继续解析。

感觉这段描述的已经蛮清晰了,不过咱们还是先看看下面的代码,展开说说细节,加深一下理解。

<!DOCTYPE html>
<html lang="zh"><head><title>Hi</title><script>console.log("Howdy ~");</script><script async src="https://google-analytics.com/analytics.js"></script><script async src="https://ads.google.cn/ad.js"></script></head><body>Hello 👋🏻 ~</body>
</html>

他的执行顺序是:

  • 在控制台打印:Howdy ~

  • 并行请求 analytics.jsad.js

  • 在页面中展示:Hello 👋🏻 ~

  • 根据网络的实际情况,以下几项会无序执行

    • 执行 analytics.js(下载完后,立即执行)

    • 执行 ad.js(下载完后,立即执行)

    • 触发 DOMContentLoaded 事件(可能在在上面 2 个脚本之前,之间,之后触发)

a4e8456dfeac467f77ab975718c7d7b3.png
script async 加载逻辑

浏览器在解析到带有 async 属性的 script 标签时,也不会阻塞页面,同样是在后台默默下载此脚本。当他下载完后,浏览器会暂停解析 HTML,立马执行此脚本。

看起来还是蛮好理解的吧?咱们再来讨论 2 个小细节:

Q1:如果设置了 async 属性的 script 下载完之后,浏览器还没解析完 HTML,会怎样?

A1:浏览器会暂停解析 HTML,立马执行此脚本,等执行完之后,再继续解析 HTML。

Q2:如果有多个 async 属性的 script 标签,那等他们下载完成之后,会按照代码顺序执行吗?

A2:不会。执行顺序是:谁先下载完成,谁先执行。async 的特点是「完全独立」,不依赖其他内容。

最佳实践:

当我们的项目,需要集成其他独立的第三方库时,可以使用此属性,他们不依赖我们,我们也不依赖于他们。通过设置此属性,让浏览器异步下载并执行他,是个不错的优化方案。

注意:

  • async 特性仅适用于外部脚本,如果 script 脚本没有 src,则会忽略 async 特性。

总结

defer

  • 不阻塞浏览器解析 HTML,等解析完 HTML 之后,才会执行 script

  • 会并行下载 JavaScript 资源。

  • 会按照 HTML 中的相对顺序执行脚本。

  • 会在脚本下载并执行完成之后,才会触发 DOMContentLoaded 事件。

  • 在脚本执行过程中,一定可以获取到 HTML 中已有的元素。

  • defer 属性对模块脚本无效。

  • 适用于:所有外部脚本(通过 src 引用的 script)。

async

  • 不阻塞浏览器解析 HTML,但是 script 下载完成后,会立即中断浏览器解析 HTML,并执行此 script

  • 会并行下载 JavaScript 资源。

  • 互相独立,谁先下载完,谁先执行,没有固定的先后顺序,不可控。

  • 由于没有确定的执行时机,所以在脚本里面可能会获取不到 HTML 中已有的元素。

  • DOMContentLoaded 事件和 script 脚本无相关性,无法确定他们的先后顺序。

  • 适用于:独立的第三方脚本。

另外:asyncdefer 之间最大的区别在于它们的执行时机。

One More Thing

你有没有想过,如果一个 script 标签同时设置 deferasync,浏览器会如何处理?

先说结论:从表现形式上来说,async 的优先级比 defer 高,也就是如果同时存在这 2 个属性,那么浏览器将会以 async 的特性去加载此脚本。

这主要分 2 种情况:

如果是「普通脚本」,浏览器会优先判断async属性是否存在,如果存在,则以async特性去加载此脚本,如果不存在,再去判断是否存在defer属性。

如果是「模块脚本[5]」,浏览器会判断async属性是否存在:

  • 如果存在,浏览器会并行下载此模块和他的所有依赖模块,等全部下载完成之后,会立刻执行此脚本。

  • 如果不存在,浏览器也会并行下载此模块和他的所有依赖模块,然后等浏览器解析完 HTML 之后,再执行此脚本。

  • 另外需要注意的是:在模块脚本上设置 defer 属性是无效的。

一图胜千言

最后,用一张图概括一下这两个属性的加载模式吧:

9766f81576b2fba19282cb015abe06ce.png
defer 和 async 的加载模式

思考题 🤔

  • 为什么浏览器在解析到普通的 script 标签时,必须先执行他?

  • 普通的 script 标签会阻塞浏览器解析 HTML,这会导致什么问题?

本文首发于:https://github.com/mrlmx/blogs/issues/4 ,如果喜欢,记得去点个赞哦~ 👍 ❤️

参考

  • https://javascript.info/script-async-defer[6]

  • https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html[7]

  • https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script[8]

  • https://html.spec.whatwg.org/multipage/scripting.html[9]

相关链接

[1]

DOMContentLoaded: https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event

[2]

外部 script: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-src

[3]

DOMContentLoaded: https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event

[4]

script type='module': https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type

[5]

模块脚本: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type

[6]

https://javascript.info/script-async-defer: https://javascript.info/script-async-defer

[7]

https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html: https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

[8]

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script

[9]

https://html.spec.whatwg.org/multipage/scripting.html: https://html.spec.whatwg.org/multipage/scripting.html


8960acaa37cc839ecd12222ae60f4980.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助5000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

596f71fba241b4a4f710904208666a46.jpeg

扫码加我微信 lxchuan12、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 lxchuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我是怎么调试 Element UI 源码的

我持续组织了近一年的源码共读活动&#xff0c;感兴趣的可以 点此扫码加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外&#xff1a;目前建…

java对象的理解

1、看生成几个对象&#xff0c;就看new了几次&#xff0c; Stu s1new Stu(“张三”); Stu s2s1;-这里也是生成了一个对象&#xff0c;只不过s1和s2指向了同一个对象 2、Stu s1new Stu(“张三”); Stu s2 new Stu(“李四”); s1s2&#xff1b;这里生成了两个对象&#xff0c;但是…

模板缓冲_模板缓冲以及如何使用它可视化体积相交

模板缓冲介绍 (Introduction) The trendy thing in real-time rendering these days is ray-tracing. However, traditional rasterization hasn’t disappeared, and it won’t in the near future. I recommend this blog post on the subject: A hybrid rendering pipeline …

重磅!哈啰 Quark Design 正式开源,下一代跨技术栈前端组件库

大家好&#xff0c;我是若川。我持续组织了近一年的源码共读活动&#xff0c;感兴趣的可以 点此扫码加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试…

对lua协程的一点理解

读《Programming In Lua》协程那一章&#xff0c;比较困惑的还是procuer-consumer那个例子&#xff1a; function consumer(prod)while true dolocal x receive(prod)print(x)end endfunction receive(prod)local status, value coroutine.resume(prod)return value endfunct…

b端 ux 设计思维_借助系统思维从视觉设计过渡到UX

b端 ux 设计思维“How can I switch to UX?” This is a common question from visual designers because there’s a lot of overlap on the surface. But it can also be a difficult transition since UX encompasses much more below the surface.“如何切换到UX&#xff…

三面面试官:运行 npm run xxx 的时候发生了什么?

大家好&#xff0c;我是若川。近期发现好些小伙伴工作有2-3年了&#xff0c;基本不会写脚手架&#xff0c;或者说没学过脚手架。对脚手架大致是如何执行的基本不太知道。其实这类学习资料真的挺多的。而且我们基本天天 npm run dev&#xff0c;应该学习内部实现。不知道的小伙伴…

figma下载_Figma的自动版式实用

figma下载Figma’s Auto Layout has been around for a while, but not everyone’s aware of the benefits it brings. It doesn’t replace constraints, they’re still very much needed. The trick is to use the right feature where necessary. I want to show you how …

Qt通过ODBC读取excel文件

之前替学校考试科用C Builder做过一个小的数据库工具&#xff0c;处理excel表格用的&#xff0c;现在想转换到Qt平台下来&#xff0c;在网上搜了搜有一些利用OBDC读取xls文件的教程&#xff1a; http://hi.baidu.com/kxw102/item/770c496d5736470ca0cf0f1d http://blog.sina.co…

真 · 三面面试官:运行 npm run xxx 的时候发生了什么?

昨晚没权限我只放了链接&#xff0c;今天联系开了白名单。昨天推文主要是为了投票&#xff0c;表明 Node.js 的重要性&#xff0c;有人评论是水文。今天重新转载下。欢迎继续点此去投票。投票显示有高达近80% 表示不太会开发脚手架&#xff0c;看来大多数人确实没有应用场景。可…

ovo svm_反思我在OVO担任远程产品设计实习生的时间

ovo svmIn a quiet bedroom accompanied only by the low humming of my laptop fan, I sat before a Google Hangouts meeting, and got to know my colleagues for the first time, unaware of the joy of a ride that was waiting for me at OVO Design.在一个安静的卧室里&…

native的Socket向Android的LocalSocketServer发送汉字乱码的问题

native的Socket发送字节流默认是GB2312的&#xff0c;所以在Java方面需要指定GB2312 byte[] buffer new byte[50]; StringBuffer strBuf new StringBuffer(); InputStream input receiver.getInputStream(); while((len input.read(buffer)) ! -1) {String newStr new Str…

最受读者喜爱的前端书 Top 15【留言送书】

最受读者喜爱的前端书Top 15JavaScript高级程序设计&#xff08;第4版&#xff09;| 中文版累计销量32万册&#xff0c;JavaScript“红宝书”全新升级 | 涵盖ECMAScript 2019&#xff0c;全面深入&#xff0c;入门和进阶俱佳 | 结合视频讲解配套编程环境&#xff0c;助你轻松掌…

图文结合简单易学的 npm 包的发布流程

大家好&#xff0c;我是若川。我持续组织了近一年的源码共读活动&#xff0c;感兴趣的可以 点此扫码加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试…

拟态防御_拟态从未消失。 这就是为什么。

拟态防御Looking back on design languages, what Apple’s WWDC 2020 Keynote means for the future of design languages, and how we move on from here.回顾设计语言&#xff0c;Apple的WWDC 2020主题演讲对设计语言的未来意味着什么&#xff0c;以及我们如何从这里继续前进…

C++二维数组做形参

二位数组作为形参&#xff0c;目前仅知道两种形式&#xff0c;一种直接采用二维数组&#xff0c;一种是用二维指针。以下是做的一个简单的实例。 大家帮我看看&#xff0c;在实际应用中两者有和优缺点。当然&#xff0c;有更好的方式更好了。 以下均应用在字符串数组中 void ar…

经常开发后台管理系统,如何提升自己?推荐~【留言送书】

大家好&#xff0c;我是若川。之前送过N次书&#xff0c;可以点此查看回馈粉丝&#xff0c;现在又和博文视点合作再次争取了几本书&#xff0c;具体送书规则看文末。Vue.js是一套用于构建用户界面的渐进式框架。与其他大型框架不同的是&#xff0c;它可以自底向上逐层应用。Vue…

lottie 动画_使用After Effects和Lottie制作网络动画而不会损失质量

lottie 动画A quick getting started guide快速入门指南 I recently took on a project where the team wanted to add some animated icons and a logo. Besides UX & UI design I am also a motion graphic designer so I took on the challenge of doing it with after…

如何编辑ttf字体文件

libfreetype的目标是以最小的内存最快的速度&#xff0c;读取和渲染字体。因此libfreetype并不适宜用来编辑ttf字体文件。编辑字体文件&#xff0c;可以用FontCreator、微软fonttools、fontforge&#xff08;苹果有个osxfonttools&#xff0c;这里不讨论&#xff09;FontCreato…

最优秀的技术能力,是技术领导力!

最近和几个刚晋升为技术经理的朋友们约饭&#xff0c;席间互相吐槽职场中的喜怒哀乐&#xff1a; “开始带团队&#xff0c;既担心自己长时间不写代码技术功底退化&#xff0c;又怕手下人干不好&#xff0c;该怎么办&#xff1f;”“我都想回去敲代码了&#xff0c;拼命熬到管理…