渲染流水线中,DOM树本身的缺陷很容易引起重排、重绘等性能问题,后来有了虚拟DOM的…


一、DOM树

1、聊一下DOM

网络进程获取到的html字节流无法直接被渲染进程理解,需要转化成渲染引擎可以理解的结构。即DOM。有以下作用:

  • 表述HTML的内部数据结构
  • 将Web⻚面和JavaScript脚本连接起来
  • DOM解析阶段即过滤一些不安全的内容

渲染引擎内部通过HTML解析器(HTMLParser)将HTML字节流转化为DOM结构

2、DOM的具体生成流程:

网络进程和渲染进程建立一个流式管道,HTML解析器直接解析, 不需要等待text/html类型的接口接受完毕再进行解析

字节流转换为DOM
图:字节流转换为DOM

  • 第一个阶段,通过分词器将字节流转换为Token
  • 第二个和第三个阶段同步进行,需要将Token解析为DOM节点,并将DOM节点添加到DOM 树中

HTML解析器开始工作时,会默认创建了一个根为document的空DOM结构。同时会将一个StartTag document的Token压入栈底。然后经过分词器解析出来的第一个StartTag html Token会 被压入到栈中,并创建一个html的DOM节点,添加到document上

解析到StartTag html时的状态
图:解析到StartTag html时的状态

解析出第一个文本Token时的状态
图:解析出第一个文本Token时的状态

元素弹出Token栈示意图
图:元素弹出Token栈示意图

3.DOM缺陷:DOM树构建被JS和CSS文件影响

JavaScript文件的下载过程会阻塞DOM解析

预解析操作:如果JavaScript文件中没有操作 DOM相关代码,就可以将该JavaScript脚本设置为异步加载,通过async 或defer来标记代码

<script async type="text/javascript" src='foo.js'></script>

JavaScript会阻塞DOM生成,而样式文件又会阻塞JavaScript的执行


二、虚拟DOM:虚拟DOM和实际的DOM有何不同?

Javascript直接操作DOM可能会引起重排、重绘等操作(强制同步布局和布局抖 动)引起性能问题。虚拟DOM作为中间层来优化dom的操作(批量更新dom,优化更新dom细节)。之后从双缓存和MVC模型的⻆度来解析了虚拟DOM

频繁DOM操作非常消耗浏览器性的,虚拟DOM核心是将批量DOM操作后的变化一次性更新到浏览器

1、DOM缺陷及虚拟DOM是如何解决的

1.DOM的一些缺陷

  • 通过 JavaScript操纵DOM是会影响到整个渲染流水线的
  • 使用DOM提供的JavaScript接口来遍历或者修改节点

以上两种操作都会引起重排、重绘或合成操作“牵一发而动全身”。另外,DOM中不当操作还可能引发强制同步布局和布局抖动问题,大大降低渲染效率。

2.虚拟DOM是如何解决这些缺陷

  • ⻚面改变的内容应用到虚拟DOM上,不是直接应用到DOM上
  • 变化应用到虚拟DOM上时,虚拟DOM并不急着去渲染⻚面,而是调整虚拟DOM的内部状态
  • 在虚拟DOM收集到足够的改变时,把这些变化一次性应用到真实的DOM上

结合React流程虚拟DOM执行流程
图:结合React流程虚拟DOM执行流程

  • 创建阶段:依据JSX和基础数据创建出来虚拟DOM,由虚拟DOM树创建出真实DOM树,再触发渲染流水线输出⻚面
  • 更新阶段:数据变更,根据新数据创建新的虚拟DOM树,结合React Fiber更新机制找出变化的地方,把变化的地方一次性更新到真实的DOM树上,触发渲染流水线

比较两个虚拟DOM的过程是在一个递归函数里执行的,其 核心算法是reconciliation。通常情况下,这个比较过程执行得很快,不过当虚拟DOM比较复杂时,执行比较函数可能占据主线程比较久的时间,导致其他任务的等待,造成⻚面卡顿。为了解决这个问题,React团队重写了reconciliation算法,新的算法称为Fiber reconciler,之前老的算法称为Stack reconciler。协程的别称就是Fiber,所谓的Fiber reconciler就是在执行算法的过程中出让主线程

2、在双缓存和MVC的视⻆来聊聊虚拟DOM

(1)双缓存
图像操作复杂的页面中,完整的画面需要多次计算完成。所以,当屏幕从前缓冲区读取数据显示的时候,可能拿到的是只计算了一部分的图像,就会造成用户看到的图像是一部分一部分显示出来的,也就是页面的闪烁。

使用双缓存,计算的中间结果存放在另一个缓冲区中,等全部的计算结束,该缓冲区已经存储了完整的图形之后,再将该缓冲区的图形数据一次性复制到显示缓冲区,这样就使得整个图像的输出非常稳定

在这里,你可以把虚拟DOM看成是DOM的一个buffer,和图形显示一样,它会在完成一次完整的操作之 后,再把结果应用到DOM上,这样就能减少一些不必要的更新,同时还能保证DOM的稳定输出

(2)MVC模式
可以把React中虚拟DOM的部分看成是一个MVC中的视图,结合Redux提供的控制器和模型构建一个MVC的模型结构,如下图所示:

基于React和Redux构建MVC模型
图:基于React和Redux构建MVC模型

  • 控制器用来监控DOM的变化,一旦DOM发生变化,控制器便会通知模型,让其更新数据
  • 模型数据更新后,控制器通知视图,告诉它模型的数据发生了变化
  • 视图接收到更新消息后,根据模型所提供的数据来生成新的虚拟DOM
  • 新的虚拟DOM生成好后,与之前的虚拟DOM进行比较,找出变化的节点
  • 比较出变化的节点后,React将变化的虚拟节点应用到DOM上,触发DOM节点更新
  • DOM节点的变化触发后续一系列渲染流水线变化,从而实现⻚面的更新

最后, 希望大家早日实现:成为编程高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!