分析网页资源如何阻塞浏览器加载
分析网页资源如何阻塞浏览器加载
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
DOMContentLoadedevent 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解析
所以,现在是不是一目了然啦!