本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。
从我们在地址栏输入网址,到页面呈现在我们的浏览器画面中,中间发生了页面加载和浏览器渲染两个过程,其中每个过程又都可以进行细分。今天我们就来讲一下浏览器页面渲染的核心流程。
页面加载过程
相信前端的同学对浏览器页面加载的过程肯定不陌生了,各大面试题中,页面加载过程都是出现频率非常高的面试题。简单要点如下:
- 浏览器根据DNS服务器得到域名的IP地址
- 向这个IP地址所在的机器或者服务器发送HTTP请求
- 服务器收到请求,处理并返回HTTP请求的内容
- 浏览器得到服务器返回的内容
例如在浏览器输入https://juejin.im/timeline,然后经过 DNS 解析,juejin.im对应的 IP 是36.248.217.149(不同时间、地点对应的 IP 可能会不同)。然后浏览器向该 IP 发送 HTTP 请求。
服务端接收到 HTTP 请求,然后经过计算(向不同的用户推送不同的内容),返回 HTTP 请求。
返回的内容是一堆HTML格式的字符串,因为只有HTML格式浏览器才能正确解析,这是W3C的标准要求。那么浏览器收到这个HTML格式的字符串之后,就是浏览器进行渲染的过程
浏览器渲染过程
从上面这个图上,我们可以看到,浏览器渲染过程如下:
解析HTML,生成DOM树,解析CSS,生成CSSOM树
将DOM树和CSSOM树结合,生成渲染树(Render Tree)
Layout(回流) :根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
Painting(重绘) :根据渲染树以及回流得到的几何信息,得到节点的绝对像素
Display: 将像素发送给GPU,最后通过调用操作系统Native GUI的API绘制,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层,这里我们不展开,之后有机会再写一篇博客来介绍)
渲染过程看起来也不复杂,让我们来具体了解下每一步具体做了什么。
构建DOM详细流程
浏览器会遵守一套步骤将HTML 文件转换为 DOM 树。宏观上,可以分为几个步骤:
浏览器从磁盘或网络读取HTML的原始字节(字节数据),并根据文件的指定编码(例如 UTF-8)将它们转换成字符串。
在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为字符串,也就是我们写的代码。
将字符串转换成Token,例如:html标签等。Token中会标识出当前Token是“开始标签”或是“结束标签”或着是“文本”等信息。
这时候你一定会有疑问,节点与节点之间的关系如何维护?
事实上,这就是Token要标识“起始标签”和“结束标签”等标识的作用。
例如“title”Token的起始标签和结束标签之间的节点肯定是属于“head”的子节点
上图给出了节点之间的关系,例如:“Hello”Token位于“title”开始标签与“title”结束标签之间,表明“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点。
事实上,构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每个Token被生成后,会立刻消耗这个Token创建出节点对象。注意:带有结束标签标识的Token不会创建节点对象。
构建CSSOM详细流程
DOM会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建CSSOM。
构建CSSOM的过程与构建DOM的过程非常相似,当浏览器接收到一段CSS,浏览器首先要做的是识别出Token,然后构建节点并生成CSSOM。
CSSOM的构建顺序跟DOM的构建顺序是一样的,但是DOM生成的是DOM树,CSSOM生成的是CSSOM树,两棵树都是骨干、节点等,非常相似。
构建渲染树
ok,现在DOM树和CSSOM树都构建完成了,那么根据上图我们知道,它们会合成一个叫渲染树的东西。在合并的过程中,不是简单的组合,例如一个DOM结点的display属性为none,那么就不会在渲染树中出现。就像我们在vue中使用的v-if和v-show,v-if为假时,不会渲染,页面没有这个元素,v-show为假时,会渲染,但不会出现在页面上,页面有这个元素,只是看不见。渲染树就是只包含可见的节点
那么当我们在渲染的过程中遇到js文件的时候会发生什么事呢?
在实际的渲染过程中,如果遇到。JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer(延迟) 或者 async(异步) 属性
JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。
原本DOM和CSSOM的构建是互不影响,井水不犯河水,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。
这是什么情况呢?
这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。因为不完整的CSSOM是无法使用的,如果JavaScript想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
布局和绘制
现在DOM树和CSSOM树合并的render树都准备就绪了,下面就是浏览器的布局和绘制。当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流(重排))。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。
布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。
总结
综上所述,我们得出这样的结论:
- 浏览器渲染流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。
- CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。
- 通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又恰巧浏览器尚未完成CSSOM的下载和构建,由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS,最后才重新DOM构建。
- 从渲染角度优化浏览器性能就是尽量减少重排和重绘、批量修改DOM元素、避免触发同步布局事件,CSS3硬件加速