从解析html构建dom树开始分析
一 概览
1、解析HTML,生成DOM树;
2、解析CSS,生成CSS规则树;
3、合并DOM树和CSS规则树,生成render树;
4、布局render树(layout/reflow);
5、绘制render树(print),绘制页面像素信息;
6、浏览器将各层的信息发送给GUI,GUI将各层合成,显示在屏幕上。
二 分析
1 构建dom树
构建过程:Bytes->Characters->Tokens->Nodes->Dom
网络中传输的内容其实是0和1这种字节数据,浏览器在收到字节数据后,才将字节数据转换为字符串;
当数据转换为字符串以后,浏览器会先将这些字符串通过词法分析转换为标记(token),这一过程叫做标记化。Token中会标识出当前Token是“开始标签”还是“结束标签”亦或是“文本”等信息。
结束化结束之后,这些标记紧接着就会被转换为Node,这些Node会根据不同Node之前的联系生成DOM树(Document Object Model)。
除了HTML文件还有CSS文件和JS文件。
2 构建CSSOM树
构建过程:Bytes->Characters->Tokens->Nodes->CSSOM
在这个过程中,浏览器会确定下一个节点的样式,并且这个过程是非常消耗资源的。因为节点的样式可以直接设置,也可以通过继承获得,浏览器必须递归CSSOM树才能确定具体的元素的样式。
3 构建渲染树(呈现树Render Tree)
合并DOM树和CSSOM树构建渲染树
1、过滤掉不可见节点(脚本标记、元标记)
2、过滤掉样式隐藏的节点(display:none)
根据渲染树来布局,计算节点的几何信息(layout)
将各个节点绘制在屏幕上(paint)
只有当DOM树和CSSOM树都构建完成之后才可以进行渲染树的构建,所以这两步是对整体渲染起阻塞作用的,当然了DOM树是必须的,它提供给页面内容,而CSSOM的必要性并不是太明显,所以在CSSOM构建的过程中可以做一些优化。在做优化前先要了解这几个知识点。
1、默认情况下,CSS是阻塞渲染的资源
2、我们可以通过媒体查询和媒体类型把一部分CSS标记为不阻塞渲染
(媒体查询的不足就是会严重影响关键渲染路径的性能)
3、浏览器会下载所有CSS资源、无论它阻塞还是不阻塞
根据上面三个知识点,会让你很清晰的知道,CSS优化可以做的事情就是,根据不同CSS使用场景和优先级的不同进行不阻塞标记。
如果是必要的CSS就请尽早的加载(1、引用位置靠前,2、减小文件体积)到客户端,这样就减少了对首次渲染的阻塞
三 JavaScript对渲染的影响
可以修改网页的方方面面,内容、样式、以及响应用户的交互。不过,javascript也会阻止DOM的构建和延缓网页渲染。下面我们来了解一下javascript和DOM、CSSOM的依赖关系。
javascript能修改内容和样式
无论(内联javascript还是外部javascript文件)都会阻止DOM的构建
DOM构建过程中如果遇到(非异步加载async)的javascript标签,浏览器将会终止DOM的构建,立即执行javascript。
这就是为什么非异步执行的javascript要放在尾部或者将可执行代码要放在DOMContentLoaded回调中?
因为如果该javascript代码操作了未构建完的DOM节点就会因为无法获取该节点而无法执行响应的操作。
CSSOM的构建影响javascript的执行
如果在浏览器尚未完成CSSOM的下载和构建时,去运行javascript脚本,那么浏览器会延迟脚本的执行和DOM的构建,直至完成CSSOM的下载和构建。可以这样理解,当出现非异步加载的javascript时,CSSOM构建完成时间是早于javascript的执行,两者早于DOMContentloaded(即DOM构建彻底完成)。
未优化–javascript正常加载
优化后–javascript异步加载
根据上面的分析,我们可以清楚的认识到,非必要优先加载的js,选择异步加载是最优选择。
为什么大家普遍把这样的代码放在body最底部?
JS文件不止会阻塞DOM的构建,也会导致CSSOM的构建。不完整的CSSOM是无法使用的,JavaScript想要访问CSSOM并更改它,就必须得到完整的CSSOM。所以导致浏览器在未完成CSSOM的构建的时候想要运行JavaScript。这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript。
JavaScript并不是必须放在底部,我们可以为script标签添加属性:
defer
属性,表示js文件会并行下载,但是会放到HTML解析完成后顺序执行。
async
属性,对于没有任何依赖的js文件可以使用,表示JS文件下载和解析不会阻塞渲染。
async与defer的区别在于,如果已经加载好,就会开始执行,即使仍在HTML解析阶段,所以这种方式加载的JavaScript依然会阻塞load事件。
async-scrapt可能在DOMContentLoaded触发直线或之后执行,但一定在load之前执行,所以多个async-script的执行顺序是不确定的。
四 优化策略
1、从文件大小考虑
2、将css放在头部,将js放在尾部
3、减少资源请求数量
4、下载的内容是否要在首屏上使用
5、script标签的使用加defer或async属性。
测试工具:Lighthouse 可以快速测试你的网页,并提供性能报告
监控工具:Nivigation Timing Api 设置你的代码,实时监控用户使用过程中的性能。
参考文档: