react中虚拟DOM,diff算法

什么是DOM:

说到虚拟DOM,就要知道什么是DOM,DOM就是结构化文本抽象表达形式,特定于web中,这个结构化文本就是html文本,html中每个元素都对应DOM中某个节点,这样因为HTML元素都逐级包含关系,DOM节点自然就构成了一个树形结构,成为DOM树。浏览器为了渲染HTML格式的网页,会先将HTML文本解析以构建dom树,然后根据DOM树渲染出用户看到都界面,当要改变界面内容都时候,去改变DOM树上都节点。

操作DOM缺点:

在前端开发中,性能消耗最大的就是DOM操作了,而且DOM操作这部分代码会让整个项目的代码变得难以维护。web前端开发中关于性能优化的一个原则就是:尽量减少DOM操作。DOM操作是非常昂贵的,虽然都是一些简单的javascript语句,但是DOM操作会引起浏览器对网页进行重新布局,重新绘制,这就是比javascript语句执行慢很多的过程,因此就出现了虚拟DOM。

虚拟DOM

如果DOM树是对HTML对抽象,那么virtual DOM就是对DOM树的抽象。virtual DOM 不会触及浏览器的部分,只存在于javascript空间的属性结构,每次数据每次更新之后自上而下渲染React组件时,重新计算虚拟DOM,对比这次的Virtual DOM 和上次渲染的Virtual DOM,对比发现差别,对发生变化对部分做批量更新,react提供了shouldComponentUpdate的生命周期回调,减少数据变化后不必要的Virtual DOM对比过程,提高性能。
虚拟DOM就好像一个虚拟空间,拥有一套virtual DOM标签,并负责虚拟节点及其属性的构建,更新,删除等工作。
虚拟dom通过标签名(tagName),节点属性(包括样式,属性,事件等),children子节点,标识id来创建

{
//标签名
tagName: 'div',
//属性
properties:{
//样式属性
//style:{}
},
//子节点
children:[]//唯一标识
key:0
}

react 中的virtual DOM 中的节点成为reactNode,分为三种类型:ReactElement,ReactFragment,ReactText,其中ReactElement又分为:ReactComponentElement,ReactDOMElement

ReactNode中不同类型节点所需要的基础元素:

type ReactNode = ReactElement | ReactFragment | ReactText
type ReactElement = ReactComponentElement | ReactDOMElement
type ReactDOMElement  = {
type: string,
props:{
     children: ReactNodeList,
     className: sting,
     ect.
},
key: sting | boolean | number | null,
ref:string | null

}
type ReactComponentElement<TProps> = {
type: ReactClass<TProps>,
props: TProps,
key: sting | boolean | number | null,
ref:string | null
}
type ReactFragment =  Array<ReactNode | ReactEmpty>;
typeReactDodeList = ReactNode | ReactEmpty;
type ReactText = string | number;
type ReactEmpty = null | undefined | boolean;

创建元素,通过jsx创建的虚拟元素最终会被编译成调用React的createElement方法
输入(jsx):

const app = <Nav color="blue"><Profile>click</Profile></Nav>

输出(javascript):

const app = React.createElement(
Nav,
{color: "blue"},
React.createElement(Profile, null, "click")
)

创建组件:
创建 文本组件:
DOM标签组件:
virtual DOM 模型涵盖了几乎所有的原生DOM标签,如div,span,p等,当开发者使用React的时候,此时的div不是原生的div标签,它其实是react生成的Virtual DOM对象,只不过标签名称相同罢了,React大部分工作都是在viirtual DOM中完成,对于原生的DOM而言,virtual DOM如同一个隔离的沙盒,因此React的处理并不是直接操作和污染原生DOM,这样不仅保持了性能上的高效和稳定,而且降低了直接操作原生DOM而导致错误的风险。
ReactDOMComponent针对Virtual DOM 标签处理主要分为以下两部分:

  • 属性的更新,包括更新样式,更新属性,处理事件等。
  • 子节点的更新,包括更新内容,更新子节点,此部分涉及diff算法。

diff算法:

diff算法是Virtual DOM 的加速器,其算法上的改进优化是React整个界面渲染的基础和性能保障。
传统的diff算法:通过循环递归对节点依次进行对比,效率低下,算法复杂度达到O(n^3),其中n是树中节点的总数,
O(n^3)到底多么可怕呢?这意味着如果要展示1000个节点,就要依次执行上十亿次的比较,这种指数型的性能消耗对前端渲染场景来说代价太高了,如今对CPU每秒钟能执行大约30亿条指令,即便是最高效的实现,也不可能在一秒内计算出差异情况。
react引入diff算法,而且设计出了稳定高效的diff算法
React将Virtual DOM 树转换成actual DOM树的最少操作过程称为调和,diff算法便是调和的具体实现。React通过制定大胆的策略,将O(n^3)复杂度问题转换成O(n)复杂度的问题。

diff策略:

  • 策略一:web UI中的DOM节点跨层级的移动操作特别少,可忽略不计;
  • 策略二:拥有相同类的两个组件会生成相似的属性结构,拥有不同类的组件 会生成不同的树形结构
  • 策略三:对于同一层级的一组子节点,他们通过唯一的id进行区分
    基于以上策略,React分别对tree diff,component diff, element diff进行算法优化

tree diff:

基于策略一,React对树算法 进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。相同层级的DOM节点进行比较,即一个父节点下 所有子节点,当发现子节点已经不存在时,则该 节点以及该节点下的所有子节点会被完全删除掉,不会进行比较,这样只需要对树进行一次遍历,便能完成整个DOM树对比较。

component diff:

React是基于组件构建应用的,对于组件间的比较采取的策略也是非常简洁,高效的。
如果是同一类型的组件,按照原策略继续比较vurtual DOM即可
如果不是,则该组件被 判断为ditry component,从而替换整个组件下的所以子节点
对于同一类型的组件,有可能其virtual DOM没有任何变化,如果能确切的知道这一点,那么可以节省大量diff运算时间,因此,react允许用户 通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析。

element diff

当节点处于同一层级时,diff提供了3种节点操作,分别为INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和REMOVE_NODE(删除)

  • INSERT_MARKUP(插入): 新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作。
  • MOVE_EXISTING(移动):旧集合中又新的组件类型,且element是可更新的类型,generateComponentChild已调用receiveComponent,这种情况下prevChild=nextChild,就需要做移动操作,可以复用以前的DOM节点。
  • REMOVE_NODE(删除):旧组件类型,在新集合里也有,但对应的element不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值