web页面解析渲染流程
整体流程

需要注意的是,这个流程是会执行很多次的,不是等到HTML全部解析完,css全部解析完再去合成渲染树、布局、绘制,一次执行完。根据这张整体流程图,有一种错误的认识是,css没有加载解析完,DOM树无法结合样式生成渲染树,页面渲染被阻塞。
实际上,部分HTML可以结合已有css,把这整个流程都执行一遍。每次HTML解析和渲染被阻塞,都会根据已经解析的HTML、css等将这个流程执行一遍,将已经解析的内容渲染出来。只要页面发生变动,需要重新计算样式、重新布局、重新绘制等等,就会执行这个流程,渲染页面。
验证这一点非常简单,我们引入一个很大的css、js文件,将网速调到3G,这样文件的加载时间比较长,但是在文件加载的时候,已经解析的部分HTML会渲染到页面上。即使将文件的引入放到head底部,导致页面主题部分一片空白,但是页面的标题因为已经解析了,所以会显示出来。


在这里我将css文件放到body内容中,可以看到在HTML加载完,css加载之前,页面将已经解析的部分HTML渲染出来了,等到css加载完成之后,又执行了一次页面的渲染流程
具体过程
要理解js、css如何阻塞页面的解析和渲染,必须理解页面解析和渲染的具体过程
页面解析渲染的过程:
- 加载HTML

- 解析HTML
解析的两个步骤:
- 1、对HTML文档进行词法分析,识别出HTML标签
- 2、根据HTML标签创建DOM对象,加入到DOM树中
解析的流程
对HTML文档从上到下逐行解析,遇到script、link这些引入外部js、css文件的标签时,停止解析,等到当前资源加载执行完毕再继续解析,并将已经解析的HTML渲染到页面上。然后使用预加载扫描器,扫描后面需要加载的外部资源预先加载,实现并发加载,如果到达并发上限则后面资源的加载需要等待。执行这个流程直到解析到html的闭合标签,HTML解析完成。

可以看到,在HTML文件加载完,两个index.css文件加载之前,Main函数进行了HTML解析渲染,页面显示出了已经解析的HTML内容。并且两个css文件是并发加载的。
外部文件加载完按照加载的先后顺序执行



对于js文件的加载以及执行和css文件加载执行的规则是一致的。但是script标签引入了defer和async两个属性:
defer会延后js的执行,等到HTML解析完,在DOMConetntLoaded事件触发前执行;
async是异步执行js,在js加载完和onload事件触发前执行,如果发生在HTML解析或渲染阶段,同样会阻塞解析渲染。
测试的时候也出现过如果加载前面的css文件时间过长,后面的js文件一直等待执行,则后面的js文件在前面的css文件解析完渲染的时候抢占执行,导致css样式等到后面的js执行完才会渲染上
DOMContentLoaded事件和onload事件的区别
- 浏览器对HTML文档逐行解析,遇到HTML闭合标签表示解析完成,HTML文档解析完成时触发domcontentloaded事件
- load表示加载,当所有的资源都加载完后触发window的onload事件
渲染会包含的步骤:
解析HTML、解析样式、重新计算样式、编译脚本、执行脚本、布局、更新图层树、绘制、合成图层
- 加载完css文件需要解析样式、重新计算样式
- 加载完js文件需要编译脚本、执行脚本
- 产生回流需要重新计算样式、布局、绘制
- 产生重绘需要重新计算样式、绘制
- 所有渲染都需要更新图层树和合成图层
js、css的引入位置对页面渲染阻塞的影响分析
js、css的执行、解析和网络加载外部资源是可以并发执行的,js执行、css解析的时候,网络可以同时去加载其他引入的js、css文件,网络加载外部引入的js、css文件不影响已经加载完的css、js文件的解析、执行。
引入位置
- 在head底部引入css文件
- 在body中引入css文件
- 在body底部引入css文件
- 在head中引入js文件
- 在body中引入js文件
- 在body底部引入js文件
html标签只包含head 和body两个标签,解析时,所有标签都会解析进这两个标签里边。body之前的任何位置都会解析进head里边,之后的都会解析进body里边。
熟悉了HTML文档解析的具体过程就很好分析js、css对页面的阻塞了,不管js、css文件的引入放在哪对页面渲染的阻塞影响是一样的——页面会渲染出script、link标签之前的内容,标签之后的内容不会渲染出来。下面说一下推荐的引入位置
在head底部引入css文件
css文件的加载和解析同样会阻塞HTML的解析和渲染,但是此时HTML只解析出了head中的内容,body中的内容不会渲染出来,而head中的内容都不会呈现在页面内容区域,等到css加载解析完,后面的渲染就可以加上页面样式了,这样不会有裸奔的内容出现。并且一开始就把样式加载解析完,会减少重新计算样式的时间,减少回流重绘,性能更好。
注意,不要以为在head底部中引入css文件就不会阻塞HTML解析和渲染,如果css文件加载解析较慢,页面会出现白屏,但是网页的标题已经出现了。
在body底部引入js文件
body底部引入js文件,在js执行(可能已经预先加载好了)的时候,除了body和html的闭合标签,页面的HTML已经解析完了,那么页面的内容都会呈现出来,当然除了js对页面的改动,这样最小化了加载执行js对页面阻塞造成的影响。
注意,此处讨论的js、css都是外部引入的文件,defer和async也只作用与外部引入的js文件。使用script和style标签写的内部js和css不会导致已经解析的HTML渲染出来,换句话说,即使在body底部使用script标签书写内部js,在js未执行完之前,页面都是白屏(文档中没有其他外部引入的css和js)。