浏览器渲染原理分析

浏览器渲染原理分析

一、前景知识

  • 浏览器引擎:浏览器有两个引擎,GUI渲染引擎和 js 引擎,这两个引擎的工作的线程是互斥的,具体原因后面再说。
  • 布局和绘制:生成渲染树后,浏览器计算元素大小和位置来 flow 生成布局,然后进行 paint 绘制,这一步合起来,叫做渲染 render
  • 重绘:对 DOM 进行样式修改,但是没有影响它的几何属性时,浏览器会进行重新绘制 repaint
  • 回流:DOM 的几何属性发送变化时(宽高、位置、层级),浏览器需要进行**重新布局 reflow **
  • 回流一定引发重绘,重绘不一定引发回流,回流成本比重绘要高得多。

二、主要流程

1. 不考虑 script 时

  1. 下载 html 文件,开始解析,解析的同时开始构建 DOM 树(值得一提的说,这是一个深度优先遍历的过程,即先构建当前节点的子节点,再去构建下一个兄弟节点)
  2. 如果解析的过程遇到了 css 文件的引用,会去下载 css 文件,并解析成 css 规则树。(值得一提的是,Dom 树的构建和 css 规则树的构建互不影响,二者是并行的
  3. 两者都解析完毕后,DOM树 和 css 树会生成渲染树,进行页面的渲染(生成布局、绘制布局)

2. 考虑 script 时

  1. 如果解析 html 的时候,遇到了 script 标签,渲染引擎就会交出控制权给 js 引擎,去进行js的解析。(猜测:被打断前,如果css规则树已经存在,那么会进行一次渲染)这里的原因是因为浏览器需要维护一个相对稳定的DOM结构,如果两个引擎同时运行那么 js 里面进行 Dom 操作会把 DOM 结构给打乱。所以,如果想首屏渲染得快,就应该把 script 标签放到 body 后面
  2. 如果 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)
  • 上述事件触发流程:
    1. readystatechange 事件(readyState == loading)
    2. readystatechange 事件 (readyState == interactive)
    3. DOMContentLoaded 事件(readyState == interactive)
    4. readystatechange 事件(readyState == complete)
    5. load 事件(readyState == complete)

2. script 加载的 async(异步下载) 和 defer(延迟执行)

  • 正常:读到立即加载
  • async 异步下载:异步加载,加载完毕立即执行,会阻塞 load 事件。可能在 DOMContentLoaded 触发之前或之后执行,但一定会在 load 之前执行。
  • defer 延迟执行:异步加载,加载完毕并且 html 文档解析完毕后执行,执行完毕后才触发 DOMContentLoaded 事件
  • 在加载多个 js 文件的时候,async 是无顺序的加载,而 defer 是有顺序的加载。HTML5标准是这样说的,但在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded 事件触发前执行(参见《JavaScript高级程序设计》2.1节 <script>元素)

3. 页面性能优化

  1. 减少回流、重绘:
    1. 不要把DOM的读写操作放到同一个语句里。浏览器其实对样式修改做了优化,浏览器会尽量把所有变动放到一个队列里一次性执行,比如连续修改同一个属性两次只会引发一次渲染,但是如果对同一个属性进行修改了又读然后又修改,就会引发两次渲染,因为第一次修改后的再读迫使页面进行重新渲染。
    2. 读取 offsetxxx、scrollxxx、clientxxx 会引发浏览器回流,尽量减少使用
    3. table 元素的回流和重绘成本高于 div
    4. 样式表越复杂,重绘和回流成本越高
    5. DOM 元素层级越高,重绘和回流成本越高
    6. 使用 cloneNode() 方法复制一个离线 DOM 出来,进行多次 DOM 操作后再插入
    7. 将元素设为 display: none(一次重新渲染),进行多次DOM操作再恢复显示(一次重新渲染),用 2 次重新渲染取代多次渲染
  2. 动画帧优化
    1. Web Worker,将与 UI 渲染无关的计算任务都放到 Worker 线程里面
    2. window.requestAnimationFrame() 方法,将代码放到下次重新渲染时执行。通过递归调用自身,就可以控制每一帧的样式。适应场景:样式读写分离、页面滚动事件(scroll)的监听函数、连续动画
    3. window.requestIdleCallback() 方法,它指定只有当一帧的末尾有空闲时间,才会执行回调函数。
  3. 文件加载优化:
    1. script 放到 body 后,或者指定 defer 属性或 async 属性进行异步加载,避免影响 html 的解析,进而优化首屏渲染。
    2. link 的 css 文件可以指定 rel = preload,指明这个文件的优先级,进而控制最优的资源加载顺序,提高渲染性能。

参考文献:
Nicholas C.Zakas——《JavaScript高级程序设计》
阮一峰——网页性能管理详解
ljianshu——深入浅出浏览器渲染原理
ljianshu——从URL输入到页面展现到底发生什么?

posted @ 2020-12-15 21:05  树干  阅读(183)  评论(0编辑  收藏  举报