导航:首页 > 源码编译 > diff算法原理是什么

diff算法原理是什么

发布时间:2023-07-20 04:05:04

① Diff算法

Diff算法的作用是用来计算出 Virtual DOM 中被改变的部分,然后针对该部分进行原生DOM操作,而不用重新渲染整个页面。
Diff算法有三大策略:

三种策略的执行顺序也是顺序依次执行。
Tree Diff 是对树每一层进行遍历,找出不同,如图1所示。

Component Diff 是数据层面的差异比较

Element Diff 真实DOM渲染,结构差异的比较

首先进行第一层比较,第一层都是R,不发生变化;然后进入第二层Component Diff,发现A组件没有,则删除A及其子组件B、C;最后比较第三层,创建A及其子组件B、C。

当节点处于同一层级时,Diff提供三种DOM操作: 删除 移动 插入

如图2所示,首先将OldVnode 和 NewVnode的首尾位置分别标记为oldS、oldE、newS、newE。

(1) oldS和newS相同,不发生变化,oldS++,newS++。

(2) newS与OldVnode不匹配,oldS前面插入f,newS++。

(3) newS与oldE相同,oldE移动到oldS前面,newS++,oldE--。

(4) newE与oldE相同,不发生变化,newE--,oldE--。

(5) 都不相同,oldS前插入newE,删除oldS,oldS++,newS++,newE--,oldE--。

(6) oldS > oldE,Diff结束,最后结果为:a、f、d、e、c。

最后附上核心源码分析:
patch

这个函数做了以下事情:

updateChildren

上一篇 虚拟DOM : https://www.jianshu.com/p/580157c93c53

② React diff算法

react 作为一款最主流的前端框架之一,在设计的时候除了简化操作之外,最注重的地方就是节省性能了。diff算法就是为 节省性能 而设计的,diff算法和虚拟DOM的完美结合是react最有魅力的地方。其中,diff 是 different 的简写,这样一来,diff 算法是什么也就顾名思义了——找不同。

在DOM需要更新的时候,通过diff算法可以 计算出 虚拟DOM 中真正变化的部分,从而只针对变化的部分进行更新渲染,避免”牵一发而动全身“,造成性能浪费。

虽然完美地实现了找不同的功能,但是傻瓜式的 循环递归对节点进行依次的对比 ,使其算法的时间复杂度为 O(n^3) ,其中n是dom树的节点数。如果dom数足够大的话,这个算法将对cpu形成绝杀。

为了优化diff算法,react中对普通的diff算法实行了三大策略,成功将时间复杂度降为 O(n)

tree diff 是diff算法的基础策略,它的重点在于 同层比较

出于对diff算法的优化,react的tree diff对DOM节点的跨层级移动的操作忽略不计,react对Virtual DOM树进行层级控制,也就是说 只对相同层级的DOM节点进行比较 (即同一个父节点下的所有子节点)。对比时,一旦发现节点不存在,则直接删除掉该节点以及之下的所有子节点。这样秩序对DOM树进行依次遍历,就可以完成整个树的对比。时间复杂度为O(n)

一个疑问:既然tree diff忽略了跨层级移动的操作,如果这种情况出现了,diff算法会怎么处理呢?

答:不管,多了就新增,少了就删除( 只有创建节点和删除节点的操作 )。所以官方明确建议不要进行DOM节点的跨层级操作。

component diff是组件间的对比

在遇到组件之间的比较时,有三种策略

优化点:

element diff 是针对同一层级的element节点的

在双方同一层级的节点对比时,有三种情况

子节点更新时,会出现以下几种情况:

react中的key值,它不是给开发者使用的。在一般情况下key值是当我们在做子元素遍历的时候需要使用的。因为我们如果要进行数据的更新,就需要进行虚拟dom的对比,而key值就是每个元素节点对应的唯一值。这个时候就需要对比子元素的key值是否有匹配项,如果有的情况下则会进行数据的更新;如果key值没有匹配项,那么这个节点就需要进行删除和重新创建。

因此我们在遍历的时候千万不要用 index下标 或者 时间戳、随机数 等进行key值的赋值。这样会造成元素的移除重新创建浪费性能。

③ React的diff算法详解

一、什么是diff算法?

为了增强用户体验,React从版本16开始将 同步更新 重构成了 可中断的异步更新 ,即采用了新的Reconciler(协调器,用于找出变化的组件),而新的Reconciler中采用了fiber架构。fiber架构的原理在此不再详细解释,我们目前只需要知道fiber节点可以保存dom信息,fiber节点构成的树叫fiber树,而更新dom是要用到‘双缓存技术’,即比较旧的fiber树与此次要渲染的jsx对象,返回新的fiber树进行渲染。 在旧fiber树与jsx对象比较时,决定哪些节点要复用的过程,就是diff算法

由于diff本身也会带来性能消耗,为了降低算法复杂度,React对diff做了 三个预设限制

更新后

如果没有key会走第二条限制,有了key,react就可以判断div和p节点是存在的,可以复用,只需要交换顺序。

diff算法会根据不同的jsx对象执行不同的处理函数,根据jsx对象的不同,我们可以分为两类

1.JSX对象(之后都用newChildren表示)的类型为object、number、string,代表同级只有一个节点
2. newChildren的类型为Array,代表同级有多个节点。

二、单节点diff

对于单节点diff,用一个流程图就可以解释

更新后

由于 key的默认值为null ,所以更新前与更新后满足key相同且元素类型不同,那么我们要删除更新前的三个div节点,新增p节点

三、多节点diff

对于多节点diff, 我们要 遍历newChildren和oldFiber 进行比较。由于React团队发现dom节点一般有更新,增加,删除这三种操作,而更新更为频繁,所以他们设置更新的优先级高于增加删除。基于以上原因,在多节点diff算法的实现中有两层遍历, 第一层遍历处理更新的节点,第二层遍历处理更新以外的节点

第一层遍历

遍历newChildren与oldFiber, 判断节点是否可复用,如果可以复用,则继续遍历。
如果不能复用,分为两种情况:

第二层遍历

第二层遍历从第一层遍历的结束位开始
第一层遍历结束后有4种结果

首先我们要判断newChildren中遍历到的节点,在oldFiber中是否存在,基于此,React将oldFiber中的节点以key-oldfiber 键值对的形式存在Map中,只需要newChildren的key,就可以判断oldFiber中有没有相应的节点。

如果oldFiber中没有相应的节点,则将newChildren生成的fiber打上placement标记

如果有相应的节点,将它的索引记为oldIndex,与上一次可复用节点在oldFiber的索引位置lastPlacedIndex比较,如果每次可复用的节点在上一次可复用右边就说明位置没有变化 ,即

oldIndex >=lastPlacedIndex, 说明相对位置没有变化 ,那么令lastPlacedIndex=oldIndex
oldIndex<lastPlacedIndex, 代表本节点需要向右移动
例如:

参考文档
React技术揭秘 (iamkasong.com)

④ 彻底理解vue的patch流程和diff算法

上一篇 《vue异步更新流程梳理》 梳理了数据从赋值到更新到视图的整体流程。但是最后的步骤 vm._update(vm._render()) 只是粗略的提了一嘴,现在就仔细的研究它内部的细节,搞清楚patch流程和diff原理是我们看源码的重中之重。

当更新数据的时候就会执行这个updateComponent方法,即方法里面的 vm._update(vm._render()) ,vm.render() 得到一个vnode,那么vm._update到底干什么? 进去看看

至此,无论是初始化还是更新都是靠patch来完成的 ,我们只需要看update流程就可以了。进入patch内部

patch函数主要接收oldVnode 与 vnode两个参数,其实就是新旧两棵虚拟树。这里经过判断条件 !isRealElement && sameVnode(oldVnode, vnode),不是真实节点 且是相同的vnode,进入patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly); 我们只要关注oldVnode, vnode这两个参数即可。

按照我们的例子,此时的oldVnode 与 vnode分别是

此处只列出关键属性tag, key, elm, children,elm,还有很多其他的属性没有列出。真实的虚拟树节点应该是如下图

我们能看出 两个vnode之间就是children[0]的不同:

追踪流程发现,我们进入oldVnode 与 vnode的children进行对比,在updateChildren函数中。

我们先不去看updateChildren的逻辑,继续看patchVnode这个函数其他的逻辑分支,得出 oldVnode 与 vnode的对比流程

总结: patchVnode这个方法的主要作用是对比两个虚拟节点过程中去更新真实dom

接下来我们进入updateChildren流程,这是两个children的对比,看一下这个函数的定义

函数解读:

下面是两个数组进行diff的流程,也就是diff算法

diff解读:
新旧两个数组,都有双端指针,两端指针向中间靠拢,直到某个数组的两端指针相交则退出循环。

在这个过程中,会先判断是否有以下四种情况

如果不符合这4种情况,那就基于旧数组遍历一次,拿到每个节点的key和index,就是oldKeyToIdx: {key1: 0, key2: 1}这种情况。然后去新数组首个节点开始匹配,匹配到就进行递归patchVnode流程,没匹配到就进行创建新节点,插入到真实dom节点里面去。

当循环结束,此时要么是旧数组相交,要么是新数组相交,只有这两种情况:

至此diff流程结束。

两个虚拟树进行对比:
patch(oldVnode, vnode) -> patchVnode(oldVnode, vnode) -> updataChildren(oldCh, newCh)
在updataChildren(oldCh, newCh)的过程中也会进行 patchVnode(oldVnode, vnode) ,如此虚拟树深度优先递归diff完成。

更加详细直观的图看此链接
https://www.processon.com/view/5e809004e4b08e4e2447d02e

阅读全文

与diff算法原理是什么相关的资料

热点内容
dvd光盘存储汉子算法 浏览:757
苹果邮件无法连接服务器地址 浏览:962
phpffmpeg转码 浏览:671
长沙好玩的解压项目 浏览:142
专属学情分析报告是什么app 浏览:564
php工程部署 浏览:833
android全屏透明 浏览:732
阿里云服务器已开通怎么办 浏览:803
光遇为什么登录时服务器已满 浏览:301
PDF分析 浏览:484
h3c光纤全工半全工设置命令 浏览:141
公司法pdf下载 浏览:381
linuxmarkdown 浏览:350
华为手机怎么多选文件夹 浏览:683
如何取消命令方块指令 浏览:349
风翼app为什么进不去了 浏览:778
im4java压缩图片 浏览:362
数据查询网站源码 浏览:150
伊克塞尔文档怎么进行加密 浏览:890
app转账是什么 浏览:163