浏览器资源的加载与解析的影响与原因
很多人对CSS、JS、媒体等资源文件的加载和解析会不会阻塞HTML的解析这类问题很模糊,即使稍微知道些,也不清楚其所以然,为了让大家理解的更深入一些,我便做了深入的研究的分析,本文就以问答的方式来解析这类问题的原由;
本文解决了以下问题,同时也是本文的目录
目录
内容
一 浏览器的渲染流程
在解释CSS和JavaScript资源的加载与解析都会阻塞什么之前,需要先简单介绍下浏览器的渲染机制,如下图:
渲染流程图
渲染流程有四个主要步骤:
- 解析HTML生成DOM树 : 渲染引擎首先解析HTML文档,生成DOM树
- 构建Render树 : 接下来不管是内联式,外联式还是嵌入式引入的CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),
- 布局Render树 : 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置
- 绘制Render树 : 最后遍历渲染树并用UI后端层将每一个节点绘制出来
以上步骤是一个渐进的过程,为了提高用户体验,渲染引擎试图尽可能快的把结果显示给最终用户。它不会等到所有HTML都被解析完才创建并布局渲染树。它会在从网络层获取文档内容的同时把已经接收到的局部内容先展示出来。
二 CSS
1 CSS的加载和解析会阻塞HTML的解析吗?
答 : CSS的加载和解析不会阻塞HTML的解析(即:不会阻塞Dom树的生成)
原因:
从上面的浏览器渲染流程中可以得出,HTML 解析成 Dom 树的过程 和 样式表解析成 CSSOM 树的过程是并行的,所以 CSS 的解析不会阻塞 HTML 的解析! 既然这样,那么 CSS 的加载 与 HTML 的解析 之间更没有依赖关系了,所以, CSS 的加载也不会阻塞 HTML 的解析;
2 CSS的加载和解析会阻塞什么?
答 : CSS的加载和解析不会阻塞HTML的解析,但会阻塞 渲染树 RenderTree 的生成,也会阻塞界面的渲染!
原因:
从上面的浏览器渲染流程中可以得出,渲染树 RenderTree 是根据 Dom 和 CSSOM 生成的,所以,在 CSSOM 还未完成之前, 是无法生成渲染树的,之后的流程更是无法进行;所以 CSS的加载和解析会阻塞 生成渲染树 RenderTre及其之后的流程;
3 对于不符合media设置的link还会加载其对应的CSS文件吗?
答 : 即使 link 标签的 media 属性的值不符合当前设备,浏览器也仍会加载其对应的CSS文件!
原因:
虽然当前设备中浏览器的状态不符合 media 属性的要求,但是接下来可能会符合 media 属性的要求,从而会用到其对应的 CSS 文件,比如:用户在浏览器上执行了 打印 操作,或者,用户改变了浏览器的尺寸,以使得浏览器匹配 media 属性的要求,等等;所以,即使 link 标签的 media 属性的值不符合当前设备,浏览器也仍会加载其对应的CSS文件,以便在接下来任何需要的时候使用!
三 JavaScript
1 JS的执行会阻塞HTML的解析吗?
答 : 所有的JS的执行,都会阻塞 HTML 的解析;即使设置了 defer 或 async 也是一样,只是设置了 defer 之后,js 会等到 HTML 解析完成之后再执行;
注意:
async 与 defer 属性对于 inline-script 都是无效的,所以下面这个示例中三个 script 标签的代码会从上到下依次执行:
<script async>console.log("1")</script>
<script async>console.log("2")</script>
<script async>console.log("3")</script>
原因:
假如有如下代码:
代码示例1:
<body>
<div id="div1">元素1</div>
<script>
var newEle = document.createElement("div");
newEle.id = "newEle";
newEle.innerText = "新元素";
document.body.appendChild(newEle);
</script>
<div id="div2">元素2</div>
<div id="div3">元素3</div>
</body>
代码中,JS往Dom树的末尾追加一个新元素;我们的预期效果是这样的:
代码示例1预期效果即:新元素 是显示在 元素1 下,被加在 script 元素下;
但是:如果在执行 JS 时不阻塞 HTML 的解析,那么当 JS 执行到 往 Dom 树中追加元素这条语句 document.body.appendChild(newEle);
时,HTML 可能已经解析到 元素3 了,则这时再追加 新元素 就会把 新元素 添加到 元素3 下,如下图所示:
所以,为了避免类似的问题发生,在执行 js 时,是要阻塞 HTML 的解析的;
2 JS的加载会阻塞HTML的解析吗?
答 : 没有设置 defer 或 async 的 js的加载会阻塞HTML的解析(即:会阻塞Dom树的生成),设置了 defer 或 async 的 js 不阻塞 HTML 的解析;
原因:
这个问题的原因和上面的 JS的执行会阻塞HTML的解析 的原因是一样的;
参考 代码示例1:在加载 没有设置 defer 或 async 的 js时,如果不阻塞 HTML 的解析的话,那么当 JS 加载完成时, HTML 可能已经解析到 元素3 了,则这时再追加 新元素 就会把 新元素 添加到 元素3 下
所以,为了避免类似的问题发生,在加载没有设置 defer 或 async 的 js时,是会阻塞HTML的解析的;
3 浏览器会并行加载多个没有设置defer或async的js资源吗?
答: 不会;浏览器同时只会加载1个没有设置 defer 或 async 的 js资源!
原因:
如果浏览器要并行加载多个 js 资源,则意味着浏览器在加载 js 资源时,不能阻塞 HTML 的解析,因为浏览器若要并行加载下面的 js 资源,那么浏览器就得继续解析下面的 HTML 标签,当解析到下面的 script 时,就会继续加载该标签对应的资源; 然而,上文已经讨论了,没有设置 defer 或 async 的 js的加载是会阻塞HTML的解析的,所以:浏览器同时只会加载1个没有设置 defer 或 async 的 js资源!
4 script标签的defer和async属性的区别
defer 与 async 属性对于 inline-script 都是无效的,所以下面这个示例中三个 script 标签的代码会从上到下依次执行;
<script async>console.log("1")</script>
<script async>console.log("2")</script>
<script async>console.log("3")</script>
对于设置了 src 属性的 script 标签,defer 与 async 会改变了 script 对HTML解析阻塞的情况,这两个属性都会使 script 异步加载,然而执行的时机是不一样的,详情见下图:
script的阻塞示意图- 蓝色线代表 script 脚本文件的网络读取过程;
- 红色线代表 script 脚本的执行过程;
- 绿色线代表 HTML 的解析过程;
5 js能获取到::before
或::after
对应的元素吗?
答: js不能获取到::before
或::after
对应的元素!
原因:
JS 能获取到的是 Dom 树上的元素,即存在于 HTML 中的元素,而 ::before
和 ::after
这样的元素并不存在于 Dom 树上,而是存在于 渲染树 RenderTree 上;渲染树是根据 Dom树 和 CSSOM 生成的,::before
或 ::after
元素 之所以存在 渲染树上,是因为 CSS 给::before
或 ::after
元素 设置了样式,所以,CSS 选择器能够选中 ::before
和 ::after
对应的元素,但 JS 不能获取其对应的 Dom 元素;
四 媒体资源
1 媒体资源(如:图片音视频等)的加载会阻塞HTML的解析吗?
答 : 媒体资源的加载不会阻塞HTML的解析!
原因:
HTML的解析不依赖于这此媒体资源,这些媒体资源也不会影响当前Dom树的结构,所以,这些媒体资源的加载不会且也没有必要阻塞HTML的解析!
2 媒体资源是并行加载的吗?
答 : 媒体资源是并行加载的!
原因:
因为媒体资源的加载不会阻塞HTML的解析,那么,浏览器加载第一个媒体资源时,HTML还可以继续往下解析,当解析到其它媒体资源的标签时,浏览器还可以继续加载相应的媒体资源,所以媒体资源是并行加载的!