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

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

[复制链接]
我来看看 发表于 2023-03-16 12:51:03 | 显示全部楼层 |阅读模式
0 y3 l) M5 ~9 Q8 |7 L( j* j

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

5 b, u" L3 a6 @2 \4 T! c

Hiconsole.log("Howdy ~"); 7 _4 v: |. X l8 I/ u4 @

# G/ r# S5 {4 j8 X8 e" Y- f

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

- I, ~8 k5 T1 V6 q/ h1 ?

> ( [6 q2 X- W; n* ^" B+ U0 { Hello ~6 |3 ~/ m8 }/ Y: e2 W4 c 他的执行顺序是:在控制台打印:Howdy ~请求并执行 vue.global.js请求并执行 vue-router.global.js

0 m" p5 N( ?& y2 n+ [

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

3 {% X5 z! p5 w) U% H# l

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

& H8 ?2 L* \4 j: V# v: d

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

. n$ L: l! e; Q) [5 t H

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

, p9 T* A' Z% p1 n8 Q# {' c8 h4 G

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

: C5 x- A% r: f3 i, j0 Q+ G

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

8 |! _( z, B1 c+ ?( R2 V

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

, Q7 O0 {1 j" V# N

.log("Howdy ~");' e1 H: _9 n4 \* p. A

9 {: H" D2 ?$ x: N( _) W" K: {1 R

>

# I3 X' { |, A# j! c

>8 R5 S8 s1 P5 @" j% t7 q8 ]7 P4 a Hello ~* F! K7 ]5 C; s. ]# A+ Y3 D 他的执行顺序是:在控制台打印:Howdy ~在页面中展示:Hello ~请求并执行 vue.global.js请求并执行 vue-router.global.js

7 Y$ {- L% t- P; z; A- f

触发 DOMContentLoaded 事件

9 m/ y$ i+ |. @- [4 q8 I! l3 K

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

3 l: U; E% c, r

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

% Z& z3 @9 O! Z

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

3 C; o% Q( d4 e4 C8 ]! v6 f

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

3 J: V9 B( }* Z; k0 s

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

2 K" o4 c* o, S

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

4 C* W3 Y8 J; {

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

- s+ X# J! t9 }. |

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

- u1 o$ a" O2 V4 B" K

Hiconsole.log("Howdy ~"); $ Q, f i3 E4 W; d2 @) D3 y( Q

# C6 o' |8 S; q0 T6 R5 O

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

$ t9 l! I4 T; e5 {, [

> & u8 Q; E/ Z5 r3 q/ S }) z4 u Hello ~; P) I0 E( c9 V# Z 他的执行顺序是:在控制台打印:Howdy ~并行请求 analytics.js 和 ad.js在页面中展示:

3 L, p+ f2 [' K. D

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

& w/ v! ]( ` _. Q- s

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

1 O+ G- W2 o, f( `. C, y _

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

. w" u7 f ~2 R( o

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

: Z. F* c( ~& J& T }

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

& Y8 ~1 T, p" i) I* t8 }

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

; t5 x' J' ^% Z) S& \+ q

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

) X _( k) g$ ]

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

$ s, o c$ l n1 S* r: N

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

7 C8 u+ U/ _, g% I' L+ q& H; d

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

4 O5 B2 B9 g/ V+ l( L" X

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

0 u* Q1 a2 o4 a K$ [

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

0 R8 M+ m8 d2 O; u

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

+ J- C' F; i6 ~. A7 i: I: m# x. Q7 ?

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

# ]7 B) `- @& p7 L3 D" v$ M" w' U

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

8 z. u( T( q0 O/ L; Z) z8 J, n

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

2 @# j9 x/ s0 |% h$ q ) U, p5 W$ ]$ h$ W" M/ t1 S) W6 ]: _ , \3 O. V9 N" Y2 z& @( a7 o* C* B# k* v( o" G 7 f8 N$ A; K+ d8 X7 e, x# Z
回复

使用道具 举报

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

    本版积分规则

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

    GMT+8, 2026-4-4 09:18 , Processed in 0.071881 second(s), 22 queries , Gzip On.

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

    Powered by Discuz! X3.5

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