分析网页资源如何阻塞浏览器加载
分析网页资源如何阻塞浏览器加载
TL; DR
- 图片/视频/字体不会阻塞页面加载
- CSS 会阻塞
DOM
渲染,但不会阻塞DOM
解析 - JS 的加载、解析、执行会阻塞
DOM
的解析 - CSS 会阻塞 JS 执行,从而 JS 阻塞
DOM
解析 DOMContentLoaded
是在DOM
完全加载以及解析,并且所有defer-script
(即<script defer src="…">
and<script type="module">
) 下载并执行后触发onload
是当页面所有资源(包括CSS
、JS
、图片、字体、视频等)都加载完成才触发defer
是“渲染完执行”,async
是“下载完执行”
问题 1:图片会造成阻塞吗?
来看以下代码
1 |
|
在 slow 的 network 环境下,打开该页面,可以看到如下结果:
当 h1
和 h2
标签渲染完成并且控制台打印了 DOMContentLoaded
的时候,图片还在加载中。这说明了图片不会阻塞 DOM
的加载,更不会阻塞页面渲染;当图片加载完成后,控制台打印 onload
,这说明图片延迟了 onload
事件的触发
视频、字体和图片其实是一样的,也不会阻塞 DOM
的加载和渲染。
问题 2:CSS 加载会阻塞渲染吗
同样模拟慢速网络环境,来看以下代码
1 |
|
可以看到如下结果:
- 当
bootstrap.css
还没加载完成的时候,页面出于空白状态,但是DOM
中出现h1
标签,说明 CSS 不会阻塞 DOM 解析 - 页面直到
bootstrap.css
加载完成才出现 h1 的内容,说明 CSS 会阻塞 DOM 的渲染
为什么是这样呢。联想一下网页渲染的过程就知道了。浏览器首先解析 html
生成 DOM
树,解析 CSS
生成 CSSOM
树,然后将 DOM
树和 CSSOM
树进行合成生成渲染树,通过渲染树进行 style
(样式计算)、layout
(布局)、paint
(绘制),最终把页面渲染出来
所以说解析 DOM
和解析 CSS
是并行执行的,既然如何,他们的解析过程就不会相互影响了,这和结论一相符;同时渲染页面实在得到 CSSOM
树之后进行的,这和结论二相符
问题 3:CSS 会阻塞 JS 执行吗
模拟慢速网络环境,来看以下代码
1 |
|
刷新浏览器的时候可以发现,在 bootstrap.css
文件加载完之后控制台打印 script
,说明 CSS 会阻塞 JS 的执行
JS 会阻塞渲染吗
1 |
|
当我们刷新浏览器的时候,页面一直没有加载出 h1
标签(白屏状态),直到 5s 后,显示 h1
标签,DOM
才渲染完成,同时打印 DOMContentLoaded
,这说明 JS
的执行会阻塞 DOM
的解析
我们再来验证下 JS 的加载是否会阻塞渲染
1 |
|
刷新页面的时候,可以看到在 vue.js
文件下载完成后,显示 h1
标签和打印 DOMContentLoaded
,这说明 JS 的加载会阻塞 DOM 的解析
可以得出结论,浏览器解析 html
遇到 script
标签的时候会发生:
- 暂停解析
DOM
- 执行
script
里的脚本,如果该script
是外链,则会先下载它,下载完成后立刻执行 - 执行完成后继续解析剩余
DOM
defer 和 async 的作用,有什么区别?
上面我们提到 JS 的加载、执行都会阻塞页面的渲染,那么有什么方法可以解决这个问题呢。
defer: 这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发
DOMContentLoaded
事件前执行。 有defer
属性的脚本会阻止DOMContentLoaded
事件,直到脚本被加载并且解析完成。
async: 对于普通脚本,如果存在
async
属性,那么普通脚本会被并行请求,并尽快解析和执行。该属性能够消除解析阻塞的 Javascript。解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后才能继续解析。
也就是说,当我们通过 defer
或者 async
的方式加载 JS 的时候,他是不会阻塞 DOM
解析的
那么,他们两者的区别是什么呢,来看一张经典的图
defer
特点
- 对于
defer
的script
,浏览器会继续解析html
,且同时下载脚本。等待html Parser
(即DOM
构建)后开始执行 defer-script
加载的 JS 代码 执行完成后触发DOMContentLoaded
事件- 多个
defer
的脚本执行顺序严格按照定义顺序进行,而不是先下载好的先执行
async
特点
- 对于
async
的script
,浏览器会继续解析html
,并同时下载脚本,一旦脚本下载完成就会立刻执行。和defer
一样,它的下载过程也是不会造成阻塞的,但是如果下载完成后DOM
还没解析完成,那么执行脚本的时候也是会造成阻塞的 async
脚本的执行 和DOMContentLoaded
的触发顺序无法明确谁先谁后,因为脚本可能在DOM
构建完成时还没下载完,也可能早就下载好了- 多个
async
,按照谁先下载完成谁先执行的原则进行,所以当它们之间有顺序依赖的时候特别容易出错
DOMContentLoaded 和 onload
在浏览器加载资源过程中涉及到两个事件,分别是 DOMContentLoaded
和 onload
,那么它们有什么区别呢
DOMContentLoaded: The
DOMContentLoaded
event fires when the HTML document has been completely parsed, and all deferred scripts (<script defer src="…">
and<script type="module">
) have downloaded and executed. It doesn’t wait for other things like images, subframes, and async scripts to finish loading.参考:https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event
onload:当页面所有资源(包括
CSS
、JS
、图片、字体、视频等)都加载完成才触发,而且它是绑定到window
对象上
参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/load_event
当 DOMContentLoaded
遇上 JS
当浏览器处理一个 HTML
文档,并在文档中遇到 <script>
标签时,就会在继续构建 DOM
之前运行它。这是一种防范措施,因为脚本可能想要修改 DOM
,甚至对其执行 document.write
操作,所以 DOMContentLoaded
必须等待脚本执行结束后才触发。以下这段代码验证了这个结论:当脚本加载完成的时候,Console
面板下才会打印出 DOMContentLoaded
1 | <script> |
那么一定是脚本执行完成后才会触发 DOMContentLoaded
嘛?答案也是否定的,有两个例外,对于 async
脚本是不会阻塞 DOMContentLoaded
触发的
当 DOMContentLoaded
遇到 CSS
前面我们已经介绍到 CSS
是不会阻塞 DOM
的解析的,所以理论上 DOMContentLoaded
应该不会等到外部样式的加载完成后才触发,这么分析是对的,让我们用下面代码进行测试一翻就知道了:
1 |
|
测试结果:当样式还没加载完成的时候,就已经打印出 DOMContentLoaded
,这和我们分析的结果是一致的。
但是一定是这样嘛?显然不一定,这里有个小坑
(基于上面代码)在样式后面再加上 <script>
标签的时候,会发现只有等样式加载完成了才会打印出 DOMContentLoaded
,为什么会这样呢?正是因为 <script>
会阻塞 DOMContentLoaded
的触发
所以当外部样式后面有脚本(async
脚本和动态脚本除外)的时候,外部样式就会阻塞 DOMContentLoaded
的触发
1 | <!-- 在上面代码基础上添加 只显示了部分内容 --> |
DOMContentLoaded
的执行时机到底在什么时候
其实还是归根到定义上,**DOMContentLoaded
是在 DOM
完全加载以及解析,并且所有defer-script
(即<script defer src="…">
and <script type="module">
) 下载并执行后触发**
联系我们前面的结论:
- CSS 不会阻塞
DOM
解析 - JS 的加载、解析、执行会阻塞
DOM
解析 - CSS 会阻塞 JS 执行,从而阻塞
DOM
解析
所以,现在是不是一目了然啦!