浏览器渲染原理分析
浏览器渲染原理分析
一、前景知识
- 浏览器引擎:浏览器有两个引擎,GUI渲染引擎和 js 引擎,这两个引擎的工作的线程是互斥的,具体原因后面再说。
- 布局和绘制:生成渲染树后,浏览器计算元素大小和位置来 flow 生成布局,然后进行 paint 绘制,这一步合起来,叫做渲染 render
- 重绘:对 DOM 进行样式修改,但是没有影响它的几何属性时,浏览器会进行重新绘制 repaint
- 回流:DOM 的几何属性发送变化时(宽高、位置、层级),浏览器需要进行**重新布局 reflow **
- 回流一定引发重绘,重绘不一定引发回流,回流成本比重绘要高得多。
二、主要流程
1. 不考虑 script 时
- 下载 html 文件,开始解析,解析的同时开始构建 DOM 树(值得一提的说,这是一个深度优先遍历的过程,即先构建当前节点的子节点,再去构建下一个兄弟节点)
- 如果解析的过程遇到了 css 文件的引用,会去下载 css 文件,并解析成 css 规则树。(值得一提的是,Dom 树的构建和 css 规则树的构建互不影响,二者是并行的)
- 两者都解析完毕后,DOM树 和 css 树会生成渲染树,进行页面的渲染(生成布局、绘制布局)
2. 考虑 script 时
- 如果解析 html 的时候,遇到了 script 标签,渲染引擎就会交出控制权给 js 引擎,去进行js的解析。(猜测:被打断前,如果css规则树已经存在,那么会进行一次渲染)这里的原因是因为浏览器需要维护一个相对稳定的DOM结构,如果两个引擎同时运行那么 js 里面进行 Dom 操作会把 DOM 结构给打乱。所以,如果想首屏渲染得快,就应该把 script 标签放到 body 后面。
- 如果 script 打断 html 解析时,同时有 css 对象模型还没有被构建完成,那么 js 的解析执行也会被打断。因为 js 可能会访问 css 对象模型,而 css 对象模型必须是一个完整的对象模型才能被访问,所以浏览器又会先去下载和构建 css 对象模型,然后再继续解析执行 js,最后再继续构建 DOM。
三、拓展
1. 页面加载的事件
- readyState 表示页面加载情况,有三种取值:
- loading:html 文档正在加载解析
- interactive:html 文档已经加载和解析完毕,子资源(images、css文件)仍在加载
- complete:html 文档和全部子资源都加载完毕了
- readystatechange 事件:readyState 改变时触发
- DOMContentLoaded 事件:readyState 为 interactive 时触发
- load 事件:readyState 为 complete 时触发(window.onload)
- 上述事件触发流程:
- readystatechange 事件(readyState == loading)
- readystatechange 事件 (readyState == interactive)
- DOMContentLoaded 事件(readyState == interactive)
- readystatechange 事件(readyState == complete)
- load 事件(readyState == complete)
2. script 加载的 async(异步下载) 和 defer(延迟执行)
- 正常:读到立即加载
- async 异步下载:异步加载,加载完毕立即执行,会阻塞 load 事件。可能在 DOMContentLoaded 触发之前或之后执行,但一定会在 load 之前执行。
- defer 延迟执行:异步加载,加载完毕并且 html 文档解析完毕后执行,执行完毕后才触发 DOMContentLoaded 事件。
- 在加载多个 js 文件的时候,async 是无顺序的加载,而 defer 是有顺序的加载。HTML5标准是这样说的,但在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded 事件触发前执行
(参见《JavaScript高级程序设计》2.1节 <script>元素)
3. 页面性能优化
- 减少回流、重绘:
- 不要把DOM的读写操作放到同一个语句里。浏览器其实对样式修改做了优化,浏览器会尽量把所有变动放到一个队列里一次性执行,比如连续修改同一个属性两次只会引发一次渲染,但是如果对同一个属性进行修改了又读然后又修改,就会引发两次渲染,因为第一次修改后的再读迫使页面进行重新渲染。
- 读取 offsetxxx、scrollxxx、clientxxx 会引发浏览器回流,尽量减少使用
- table 元素的回流和重绘成本高于 div
- 样式表越复杂,重绘和回流成本越高
- DOM 元素层级越高,重绘和回流成本越高
- 使用 cloneNode() 方法复制一个离线 DOM 出来,进行多次 DOM 操作后再插入
- 将元素设为 display: none(一次重新渲染),进行多次DOM操作再恢复显示(一次重新渲染),用 2 次重新渲染取代多次渲染
- 动画帧优化
- Web Worker,将与 UI 渲染无关的计算任务都放到 Worker 线程里面
- window.requestAnimationFrame() 方法,将代码放到下次重新渲染时执行。通过递归调用自身,就可以控制每一帧的样式。适应场景:样式读写分离、页面滚动事件(scroll)的监听函数、连续动画
- window.requestIdleCallback() 方法,它指定只有当一帧的末尾有空闲时间,才会执行回调函数。
- 文件加载优化:
- script 放到 body 后,或者指定 defer 属性或 async 属性进行异步加载,避免影响 html 的解析,进而优化首屏渲染。
- link 的 css 文件可以指定 rel = preload,指明这个文件的优先级,进而控制最优的资源加载顺序,提高渲染性能。
参考文献:
Nicholas C.Zakas——《JavaScript高级程序设计》
阮一峰——网页性能管理详解
ljianshu——深入浅出浏览器渲染原理
ljianshu——从URL输入到页面展现到底发生什么?