react虚拟dom机制与diff算法

React的一个突出特点是拥有极速地渲染性能。该功能依靠的就是facebook研发团队弄出的虚拟dom机制以及其独特的diff算法。下面简单解释一下react虚拟dom机制和diff算法的实现思想:
要讲虚拟dom机制必须提到一个概念——虚拟dom树,这是react在真实dom树基础上建立的一个抽象的树,应用、虚拟dom与真实dom的关系如下图显示:
这里写图片描述
而标准的dom机制如下图所示:
这里写图片描述
对比两个图就可以发现标准dom机制下,用户在应用上的操作是直接对真实dom进行操作的,而在react应用中,用户在应用中对dom的操作其实是对虚拟dom的操作,用户的操作产生的数据改变或者state变量改变(此处的改变具体的讲就是事件函数对dom的操作)都会保存到虚拟dom上,之后再批量的对这些更改进行diff算法计算,对比操作前后的虚拟dom树,把更改后的变化再同步到真实dom上。举个例子:
标准dom机制下对某一节点在事件函数中做如下操作:

 var A=document.getElementById('test');
 A.style.backgroundColor = "black";
 A.style.backgroundColor = "red";
 A.style.backgroundColor = "black";

如上所示,在标准dom机制下,会对A节点进行三次的dom操作。
而在react应用的事件函数中进行如上操作时,同样会在虚拟dom上进行三次dom的操作,但在真实dom中,它只会执行一次dom操作,即A.style.backgroundColor = “black”;因为在react虚拟dom机制中,它会把所有的操作都会合并,只会对比刚开始的状态和最后操作的状态,两者中找出不同再同步到真实dom中,这就大大减少了真实dom的操作,而众所周知,dom操作是很耗性能的,这是react能做到极速渲染的原因之一。

另外一个原因就是react独特的diff算法,同样给出标准diff算法和react diff算法的描述,对比了就会明白了:
首先讲一下diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层对比,如下图:
这里写图片描述
在标准dom机制下在同一位置对比前后的dom节点,发现节点改变了,会继续比较该节点的子节点,一层层对比,找到不同的节点,然后更新节点。

在react的diff算法下,在同一位置对比前后dom节点,只要发现不同,就会删除操作前的domm节点(包括其子节点),替换为操作后的dom节点。

对比两种diff算法,大家可以发现,react的diff算法下,当dom节点更改时,会大大减少dom树的节点遍历,这也是其另外一个可以实现极速渲染的一个原因。

diff策略
Web UI 中DOM节点跨层级的移动操作特别少,可以忽略不计
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
对于同一层级的一组子节点,它们可以通过唯一id进行区分。
对于以上三个策略,react分别对tree diff,component diff,element diff进行算法优化。
1.tree diff
基于策略一,WebUI中DOM节点跨层级的移动操作少的可以忽略不计,React对Virtual DOM树进行层级控制,只会对相同层级的DOM节点进行比较,即同一个父元素下的所有子节点,当发现节点已经不存在了,则会删除掉该节点下所有的子节点,不会再进行比较。这样只需要对DOM树进行一次遍历,就可以完成整个树的比较。复杂度变为O(n);

疑问:当我们的DOM节点进行跨层级操作时,diff会有怎么样的表现呢?

如下图所示,A节点及其子节点被整个移动到D节点下面去,由于React只会简单的考虑同级节点的位置变换,而对于不同层级的节点,只有创建和删除操作,所以当根节点发现A节点消失了,就会删除A节点及其子节点,当D发现多了一个子节点A,就会创建新的A作为其子节点。

由此可以发现,当出现节点跨层级移动时,并不会出现想象中的移动操作,而是会进行删除,重新创建的动作,这是一种很影响React性能的操作。因此官方也不建议进行DOM节点跨层级的操作。

2.componnet diff
React是基于组件构建应用的,对于组件间的比较所采用的策略也是非常简洁和高效的。
如果是同一个类型的组件,则按照原策略进行Virtual DOM比较。
如果不是同一类型的组件,则将其判断为dirty component,从而替换整个组价下的所有子节点。
如果是同一个类型的组件,有可能经过一轮Virtual DOM比较下来,并没有发生变化。如果我们能够提前确切知道这一点,那么就可以省下大量的diff运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析。

当组件D变为组件G时,即使这两个组件结构相似,一旦React判断D和G是不用类型的组件,就不会比较两者的结构,而是直接删除组件D,重新创建组件G及其子节点。虽然当两个组件是不同类型但结构相似时,进行diff算法分析会影响性能,但是毕竟不同类型的组件存在相似DOM树的情况在实际开发过程中很少出现,因此这种极端因素很难在实际开发过程中造成重大影响。

3.element diff
当节点属于同一层级时,diff提供了3种节点操作,分别为INSERT_MARKUP(插入),MOVE_EXISTING(移动),REMOVE_NODE(删除)。
INSERT_MARKUP:新的组件类型不在旧集合中,即全新的节点,需要对新节点进行插入操作。
MOVE_EXISTING:旧集合中有新组件类型,且element是可更新的类型,这时候就需要做移动操作,可以复用以前的DOM节点。
REMOVE_NODE:旧组件类型,在新集合里也有,但对应的element不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值