找回密码
 加入怎通
查看: 150|回复: 0

浏览器是如何通过 defer 与 async 属性,优化页面加载速度的?(浏览器是百度吗)

[复制链接]
我来看看 发表于 2023-03-16 12:51:03 | 显示全部楼层 |阅读模式
( o1 F/ p% D" Y8 d t: U

前言在面试的时候,经常会遇到一道经典的面试题:如何优化网页加载速度?常规的回答中总会有一条:把 css 文件放在页面顶部,把 js 文件放在页面底部那么,为什么要把 js 文件放在页面的最底部呢?我们先来看下这段代码:。

* f* Y. N6 e6 e* V) X! \

Hiconsole.log("Howdy ~"); $ L- Y2 `! I: I _9 ]( t [) m

! d# K6 O/ L! U

src="https://unpkg.com/vue@3.2.41/dist/vue.global.js">

. S$ i- k! g8 j: F I

>6 i, o$ A3 N" j9 f0 e) F Hello ~- K* x$ L/ d( M/ ^. v5 H* I 他的执行顺序是:在控制台打印:Howdy ~请求并执行 vue.global.js请求并执行 vue-router.global.js

/ e {5 a; c/ g; u* n/ F8 D6 A

在页面中展示:Hello ~触发 DOMContentLoaded 事件

. b+ B8 |" F8 D$ p4 Y

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

' i& v. I4 o" v+ l9 y: h

如果请求并执行「vue.global.js」需要 3 秒,「vue-router.global.js」需要 2 秒,那么页面中的 Hello ~,则至少需要 5 秒以上才会展示出来可以看到,script 标签会阻塞浏览器解析 HTML,如果把 。

+ |5 C6 S# T; o2 P) S0 l! k3 J" U* z

script 都放在 head 中,在网络不佳的情况下,就会导致页面长期处于白屏状态在很久以前,一般都是将这些外联脚本,放在 body 标签的最后面,确保先解析展示 body 中的内容,然后再一个个请求执行这些外联脚本。

$ I, O$ I# C" e1 ^

那有没有其他更优雅的解决方案呢?答案是肯定的,现在 script 标签新增了 2 个属性:defer 和 async,就是为了解决此类问题,提升页面性能的先看一下 MDN 上的解释:。

. {" I d( R1 m4 s6 m8 U3 H3 b

这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行 有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。

/ e z2 C$ c& l

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

. e# q- |4 q |8 Y, w# Z9 ~' n

.log("Howdy ~"); 9 D3 `, T4 {4 K) X1 S

@9 u$ {) v: r9 e+ w6 A8 L

>

7 g* T& Q5 y+ x! \8 L

>; a* Z% K; \( `' Z. q" ^1 q3 O Hello ~7 l6 F! b5 d' L# L8 E 他的执行顺序是:在控制台打印:Howdy ~在页面中展示:Hello ~请求并执行 vue.global.js请求并执行 vue-router.global.js

6 A" K2 H: Q" R/ X/ B4 T

触发 DOMContentLoaded 事件

* C% I" j* J. ]) k6 h

script defer 加载逻辑如果在 script 标签上设置了 defer 属性,那么在浏览器解析到这里时,会默默的在后台开始下载此脚本,并继续解析后面的 HTML,并不会阻塞解析操作等到 HTML 解析完成之后,浏览器会立即执行后台下载的脚本,脚本执行完成之后,才会触发 。

- s' O! x# j# Q) j: m& i

DOMContentLoaded 事件看起来还是蛮好理解的吧?咱们再来讨论 2 个小细节:Q1: 如果 HTML 解析完成之后,设置了 defer 属性的脚本还没下载完成,会怎样?A1: 浏览器会等脚本下载完成之后,再执行此脚本,执行完成之后,再触发 。

) } K! Q2 p$ Q; u5 `& @

DOMContentLoaded 事件Q2: 如果有多个设置了 defer 属性的脚本,那浏览器会如何处理?A2: 浏览器会并行的在后台下载这些脚本,等 HTML 解析完成,并且所有脚本下载完成之后,再按照他们在 HTML 中出现的相对顺序执行,等所有脚本执行完成之后,再触发 。

7 X; |; }: W- {. Y& u$ g: }; \

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

4 M% N! j4 ]4 F2 J

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

1 D$ W9 ^, _% j! X; Q* O' s6 k

注意:defer 属性仅适用于外部脚本,如果 script 脚本没有 src,则会忽略 defer 特性defer 属性对模块脚本(script type=module)无效,因为模块脚本就是以 defer。

: n; p1 T, V& ^* M

的形式加载的按照惯例,先看一下 MDN 上的解释:对于普通脚本,如果存在 async 属性,那么普通脚本会被并行请求,并尽快解析和执行 对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。

( P6 H) @6 l' b; m6 z# _! m

该属性能够消除解析阻塞的 Javascript 解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后才能继续解析感觉这段描述的已经蛮清晰了,不过咱们还是先看看下面的代码,展开说说细节,加深一下理解。

- T4 B6 i5 v5 h: f0 E1 U( O$ @

Hiconsole.log("Howdy ~"); : z% P7 z. d$ t# G7 d6 k

3 X& x6 |9 U+ q: _

asyncsrc="https://google-analytics.com/analytics.js">

( _" A0 n/ i5 x' y `" }& X

> 3 B" a1 w3 S9 s" B Hello ~6 |- \) w: X/ p( {1 H9 [ 他的执行顺序是:在控制台打印:Howdy ~并行请求 analytics.js 和 ad.js在页面中展示:

/ R9 h+ _7 u+ M/ m1 A3 y

Hello ~根据网络的实际情况,以下几项会无序执行 执行 analytics.js(下载完后,立即执行)执行 ad.js(下载完后,立即执行)触发 DOMContentLoaded 事件(可能在在上面 2 个脚本之前,之间,之后触发)

/ o5 `. v: S9 z) X6 x

script async 加载逻辑浏览器在解析到带有 async 属性的 script 标签时,也不会阻塞页面,同样是在后台默默下载此脚本当他下载完后,浏览器会暂停解析 HTML,立马执行此脚本看起来还是蛮好理解的吧?咱们再来讨论 2 个小细节:。

8 N7 u5 V3 z3 E( ]5 I

Q1:如果设置了 async 属性的 script 下载完之后,浏览器还没解析完 HTML,会怎样?A1:浏览器会暂停解析 HTML,立马执行此脚本,等执行完之后,再继续解析 HTMLQ2:如果有多个 。

2 y3 h+ K4 t! g

async 属性的 script 标签,那等他们下载完成之后,会按照代码顺序执行吗?A2:不会执行顺序是:谁先下载完成,谁先执行async 的特点是「完全独立」,不依赖其他内容最佳实践:当我们的项目,需要集成其他独立的第三方库时,可以使用此属性,他们不依赖我们,我们也不依赖于他们。

. t$ n- C5 i3 R' d

通过设置此属性,让浏览器异步下载并执行他,是个不错的优化方案注意:async 特性仅适用于外部脚本,如果 script 脚本没有 src,则会忽略 async 特性总结defer不阻塞浏览器解析 HTML,等解析完 HTML 之后,才会执行 。

+ B4 T. }$ U+ J5 j

script会并行下载 JavaScript 资源会按照 HTML 中的相对顺序执行脚本会在脚本下载并执行完成之后,才会触发 DOMContentLoaded 事件在脚本执行过程中,一定可以获取到 HTML 中已有的元素。

; l' W- ^, X$ W* f; L

defer 属性对模块脚本无效适用于:所有外部脚本(通过 src 引用的 script)async不阻塞浏览器解析 HTML,但是 script 下载完成后,会立即中断浏览器解析 HTML,并执行此 script

$ ?/ K8 C6 ^9 R

会并行下载 JavaScript 资源互相独立,谁先下载完,谁先执行,没有固定的先后顺序,不可控由于没有确定的执行时机,所以在脚本里面可能会获取不到 HTML 中已有的元素DOMContentLoaded。

4 h3 o% h/ W1 i7 Z j9 D

事件和 script 脚本无相关性,无法确定他们的先后顺序适用于:独立的第三方脚本另外:async 和 defer 之间最大的区别在于它们的执行时机One More Thing你有没有想过,如果一个 。

9 n8 z" i0 V4 x) I

script 标签同时设置 defer 和 async,浏览器会如何处理?先说结论:从表现形式上来说,async 的优先级比 defer 高,也就是如果同时存在这 2 个属性,那么浏览器将会以 async

1 P; U' K- `( k3 |

的特性去加载此脚本这主要分 2 种情况: 如果是「普通脚本」,浏览器会优先判断async属性是否存在,如果存在,则以async特性去加载此脚本,如果不存在,再去判断是否存在defer属性如果是「模块脚本」,浏览器会判断。

$ c; S5 E. |2 e& t

async属性是否存在:如果存在,浏览器会并行下载此模块和他的所有依赖模块,等全部下载完成之后,会立刻执行此脚本如果不存在,浏览器也会并行下载此模块和他的所有依赖模块,然后等浏览器解析完 HTML 之后,再执行此脚本。

1 L0 }& j x* ^& o3 x; O* K% m

另外需要注意的是:在模块脚本上设置 defer 属性是无效的。一图胜千言最后,用一张图概括一下这两个属性的加载模式吧:

4 D, ^$ I+ w8 ^

defer 和 async 的加载模式思考题 为什么浏览器在解析到普通的 script 标签时,必须先执行他?普通的 script 标签会阻塞浏览器解析 HTML,这会导致什么问题?本文首发于:https://github.com/mrlmx/blogs/issues/4 ,如果喜欢,记得去点个赞哦~ ❤️

6 V# x# a9 A9 I0 b) K# j" }5 J

参考https://javascript.info/script-async-deferhttps://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

( p! _; A; K+ v/ }5 l

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/scripthttps://html.spec.whatwg.org/multipage/scripting.html

; u$ p& y0 ]; v/ h- I. D0 s0 _ 8 a- v. B4 l5 C( _/ M' T2 V 9 X$ o1 B5 r/ V2 u8 D8 P . l! B" O) T' h# {& m/ h* Q; n0 ]
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 加入怎通

    本版积分规则

    QQ|手机版|小黑屋|网站地图|真牛社区 ( 苏ICP备2023040716号-2 )

    GMT+8, 2026-7-8 18:32 , Processed in 0.036762 second(s), 25 queries , Gzip On.

    免责声明:本站信息来自互联网,本站不对其内容真实性负责,如有侵权等情况请联系420897364#qq.com(把#换成@)删除。

    Powered by Discuz! X3.5

    快速回复 返回顶部 返回列表