前语

都说“双端比照算法”,那么双端比照算法,究竟是怎样样的呢?跟 React 中的 Diff 算法又有什么不同呢?

要了解这些,咱们先了解 React 中的 Diff 算法,然后再了解 Vue3 中的 Diff 算法,终究讲一下 Vue2 中的 Diff 算法,才能去比较一下他们的差异。

终究讲一下为什么 Vue 中不需求运用 Fiber 架构。

React 官方的解析

其实为什么 React 不选用 Vue 的双端比照算法,React 官方现已在源码的注释里现已阐明了,咱们来看一下 React 官方是怎样说的。

function reconcileChildrenArray(
returnFiber: Fiber,
 currentFirstChild: Fiber | null,
 newChildren: Array<*>,
 expirationTime: ExpirationTime,
): Fiber | null {
    // This algorithm can't optimize by searching from boths ends since we
    // don't have backpointers on fibers. I'm trying to see how far we can get
    // with that model. If it ends up not being worth the tradeoffs, we can
    // add it later.
    // Even with a two ended optimization, we'd want to optimize for the case
    // where there are few changes and brute force the comparison instead of
    // going for the Map. It'd like to explore hitting that path first in
    // forward-only mode and only go for the Map once we notice that we need
    // lots of look ahead. This doesn't handle reversal as well as two ended
    // search but that's unusual. Besides, for the two ended optimization to
    // work on Iterables, we'd need to copy the whole set.
    // In this first iteration, we'll just live with hitting the bad case
    // (adding everything to a Map) in for every insert/move.
    // If you change this code, also update reconcileChildrenIterator() which
    // uses the same algorithm.

大约的意思便是说:

React 不能经过双端比照进行 Diff 算法优化是由于现在 Fiber 上没有设置反向链表,而且想知道就现在这种方案能持续多久,假如现在这种形式不抱负的话,那么也能够添加双端比照算法。

即使是双端比照算法,咱们也要对这种状况进行优化,咱们应该运用 Map 这种数据结构方案去代替原来那种几乎没有什么改变也进行暴力比较的方案。它第一次查找循环是经过 forward-only 这种形式(便是只从左向右查找),(第一次循环或许还没有结束,还有节点没有比对的时分)假如还要持续向前循环查找那么就要经过 Map 这种数据类型了。(就现在这个单向链表的数据结构,假如选用)双端比照查找算法比较难控制它反向查找的,但它确实是一种成功的算法。此外,双端比照算法的完结也在咱们的作业迭代当中。

第一次迭代,咱们就先将就运用这种不好的方案吧,每次新增/移动都要添加一切的数据到一个 Map 的数据类型对象中。

“we’d need to copy the whole set”,这一句,每一个单词都懂,但便是不知道他想说什么,所以就不翻译了,有知道的大神吗?

本人水平有限,讹夺难免,如有讹夺,恳请各位指正。

React 的官方尽管解析了,但咱们想要完全了解究竟为什么,仍是要去具体了解 React 的 Diff 算法是怎样样的。在了解 React Diff 算法之前,咱们首要要了解什么是 Fiber,为什么 React 中要运用 Fiber?

Fiber 的结构

在 React15 曾经 React 的组件更新创立虚拟 DOM 和 Diff 的进程是不可中止,假如需求更新组件树层级十分深的话,在 Diff 的进程会十分占用浏览器的线程,而咱们都知道浏览器履行JavaScript 的线程和烘托实在 DOM 的线程是互斥的,也便是同一时刻内,浏览器要么在履行 JavaScript 的代码运算,要么在烘托页面,假如 JavaScript 的代码运行时刻过长则会造成页面卡顿。 依据以上原因 React 团队在 React16 之后就改写了整个架构,将原来数组结构的虚拟DOM,改成叫 Fiber 的一种数据结构,依据这种 Fiber 的数据结构能够完结由原来不可中止的更新进程变成异步的可中止的更新。

Fiber 的数据结构首要长成以下的姿态,首要经过 Fiber 的一些特点去保存组件相关的信息。

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 作为静态数据结构的特点
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;
  // 用于衔接其他Fiber节点形成Fiber树
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;
  this.ref = null;
  // 作为动态的作业单元的特点
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;
  this.mode = mode;
  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;
  // 调度优先级相关
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  // 指向该fiber在另一次更新时对应的fiber
  this.alternate = null;
}

Fiber 首要靠以下特点连成一棵树结构的数据的,也便是 Fiber 链表。

// 指向父级Fiber节点
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟Fiber节点
this.sibling = null;

举个比方,如下的组件结构:

function App() {
  return (
    <div>
      i am
      <span>Coboy</span>
    </div>
  )
}

对应的 Fiber 链表结构:

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

那么以上的 Fiber 链表的数据结构有什么特点,便是任何一个方位的 Fiber 节点,都能够十分简略知道它的父 Fiber, 第一个子元素的 Fiber,和它的兄弟节点 Fiber。却不简略知道它前一个 Fiber 节点是谁,这便是 React 中单向链表 Fiber 节点的特点。也正是由于这些即便在和谐的进程被中止了,再恢复和谐的时分,仍然知道当时的 父节点和孩子节点等信息。

那么 React 是将对应组件怎样生成一个 Fiber 链表数据的呢?

Fiber 链表的生成

上面的组件在经过 JSX 的编译之后,初始化的时分会生成成一个类似于 React 15 或者 Vue 那种虚拟 DOM 的数据结构。然后创立一个叫 fiberRoot 的 Fiber 节点,然后开端从 fiberRoot 这个根 Fiber 开端进行和谐,生成一棵 Fiber 树,这个棵树被称为:workInProgress Fiber 树 ,意思是正在作业的 Fiber 树,接下来咱们具体了解一下具体是怎样生成一棵 Fiber 树的。要先了解 Fiber 树的生成原理才更好去了解 Fiber 树 diff 的进程。

以下是一段简略版的 Fiber 链表生成的代码片段。 这个和谐子节点的函数接纳两个参数,returnFiber 是父 Fiber,children 是这个节点的子元素的虚拟 DOM数据。

// 这个和谐子节点的函数接纳两个参数,returnFiber 是父 Fiber,children 是这个节点的子元素的虚拟 DOM数据。
export function reconcileChildren(returnFiber, children) {
    // 假如是字符串或者数字则不创立 Fiber
    if(isStringOrNumber(children)) {
        return
    }
    const newChildren = isArray(children) ? children : [children]
    // 上一轮的 fiber 节点
    let previousNewFiber = null;
    // 初度烘托(false)仍是更新(true)
    let shouldTrackSideEffects = !!returnFiber.alternate
    // 老 Fiber 节点
    let oldFiber = returnFiber.alternate && returnFiber.alternate.child
    let nextOldFiber = null
    // 上一次和谐回来的方位
    let lastPlacedIndex = 0;
    // 记载每个 fiber 节点的方位
    let newIdx = 0;
    // 假如不存在老 Fiber 则是初始化的进程,进行 Fiber 链表的创立
    if(!oldFiber) {
        for (; newIdx < newChildren.length; newIdx++) {
            // 获取节点元素内容
            const newChild = newChildren[newIdx]
            // 假如节点为 null 则不需求创立 fiber 节点
            if(newChild === null) {
                continue
            }
            // 创立新 fiber 的时分记载了关键的父 fiber 等重要信息
            const newFiber = createFiber(newChild, returnFiber)
            // 记载当时每一个 fiber 的方位
            lastPlacedIndex = placeChild(
                newFiber,
                lastPlacedIndex,
                newIdx,
                shouldTrackSideEffects // 初度烘托(false)仍是更新(true)
            )
		    // 当上一轮的 fiber 节点为 null 的时分,这一轮的 fiber 便是头节点
            if(previousNewFiber === null) {
                // 父 fiber 的 child 便是第一个节点
                returnFiber.child = newFiber
            } else {
                // 假如不是第一个节点,那么便是兄弟节点
                // 上一轮 fiber 的兄弟节点是这一轮的 fiber 节点
                previousNewFiber.sibling = newFiber;
            }
		   // 记载上一轮的 fiber,既是这一轮的 fiber 便是下一轮的上一轮 fiber
            previousNewFiber = newFiber
        }
        return
    }
}

构建完的 workInProgress Fiber树 会在 commit阶段 烘托到页面。

在组件状况数据产生改变的时分,会依据最新的状况数据先会生成新的虚拟DOM,再去构建一棵新的 workInProgress Fiber 树 ,而在重新和谐构建新的 Fiber 树的进程也便是 React Diff 产生的地方。接下来,咱们就看看 React Diff 算法是怎样样的。

React 的 Diff 算法

深度优先,有子节点,就遍历子节点,没有子节点,就找兄弟节点,没有兄弟节点,就找叔叔节点,叔叔节点也没有的话,就持续往上找,它爷爷的兄弟,假如一向没找到,就代表一切的更新使命都更新结束了。

重点是在更新自己的一起需求去和谐子节点,也便是传说中进行 Diff 的地方

进入和谐的时分它自己便是父 Fiber,它的子节点在和谐之前,是刚刚经过更新的状况数据生成的最新的虚拟DOM数据,是个数组结构的元素数据。

那么要进行更新,就肯定是认为最新的节点数据为准了,又由于最新的节点数据是一个数组,所以能够进行循环比照每一个节点,很明显这个循环是从左向右进行查找比对的。

第一轮,常见状况的比对

那么第一个节点的老 Fiber 怎样拿到呢?能够经过 父 Fiber 的 child 特点拿到,这样第一个节点的老 Fiber 就拿到了,那么第二节点的老 Fiber,很明显能够经过第一个节点的老 Fiber 节点的 sibling 特点拿到,后边的以此类推。

怎样比对呢?

在循环的新节点虚拟DOM数据的时分,拿到新节点虚拟DOM信息,然后就去和老 Fiber 节点进行比对,假如两个节点相同则创立一个新的 Fiber 节点并复用一些老 Fiber 节点的信息,比方实在 DOM,并给这个新的 Fiber 节点打上一个 Update 的符号,代表这个节点需求更新即可。

接着去更新和谐方位信息。

在循环的终究进行 Fiber 链表的处理:

假如是头节点,则把新 Fiber 设置为父 Fiber 的 child 特点的值; 假如不是头节点,则把新 Fiber 设置为上一轮循环的创立的 Fiber 节点的 sibing 特点的值; 更新上一轮 Fiber 变量的值,便是把这一轮的 Fiber 设置成下一轮的 Fiber; 更新比对的老 Fiber 的值。

假如新节点都能找到能复用的节点,则判别是否还存在老节点,有则删去。

第二轮,不常见的状况的比对

假如经过第一轮比对,新节点还存在未比对的,则持续循环查找。

先将剩下未比对的老 Fiber 节点悉数处理成一个 老 Fiber 的 key 或老 Fiber 的 index 为 key,Fiber 节点为 value 的 Map 中,这样就能够,以 O(1) 杂乱度,经过新 Fiber 的 key 去 Map 对象中查找匹配的 Fiber,找到了,则删去 Map 对象中的老 Fiber 数据,然后复用匹配到的 Fiber 数据。

接下来,不管有没有匹配到都进行方位和谐,记载最新的方位信息,新增的 Fiber 由于没有存在老 Fiber 而会被打上 Placement 的符号,在将来提交的阶段将会被进行新增操作。这个进程跟第一轮终究的处理是相同的。

在循环的终究进行 Fiber 链表的处理:

假如是头节点,则把新 Fiber 设置为父 Fiber 的 child 特点的值; 假如不是头节点,则把新 Fiber 设置为上一轮循环的创立的 Fiber 节点的 sibing 特点的值; 更新上一轮 Fiber 变量的值,便是把这一轮的 Fiber 设置成下一轮的 Fiber; 更新比对的老 Fiber 的值。

重点怎么和谐更新方位信息

假如是初始烘托,那么和谐方位就仅仅记载当时元素下标的方位到 Fiber 节点上。假如是更新阶段,就先判别有没有老 Fiber 节点,假如没有老 Fiber 节点,则阐明该节点需求创立,就给当时新的 Fiber 节点打上一个 Placement 的符号,假如有老 Fiber 节点,则判别老 Fiber 节点的方位是否比上一次和谐的回来的方位小,假如是,则阐明该节点需求移动,给新 Fiber 节点打上一个 Placement 的符号,并持续回来上一次和谐回来的方位;假如老 Fiber 节点的方位大或者等于上一次和谐回来的方位,则阐明该节点不需求进行方位移动操作,就回来老 Fiber 的方位即可。

这里需求阐明的一点,为什么移动和新增节点都是 Placement 的符号呢?

由于咱们是在和谐一个子节点列表,所以不管是新增仍是移动都是归于方位是需求产生改变的,所以新增和移动都是同一种操作状况。

小结

总个来说,React Diff 算法分以下几个步骤:

  1. 第一轮,从左向右新老节点进行比对查找能复用的旧节点,假如有新老节点比对不成功的,则中止这一轮的比对,并记载了中止的方位。
  2. 假如第一轮比对,能把一切的新节点都比对结束,则删去旧节点还没进行比对的节点。
  3. 假如第一轮的比对,没能将一切的新节点都比对结束,则持续从第一轮比对中止的方位持续开端循环新节点,拿每一个新节点去老节点里边进行查找,有匹配成功的则复用,没匹配成功的则在和谐方位的时分打上 Placement 的符号。
  4. 在一切新节点比对结束之后,查看还有没有没进行复用的旧节点,假如有,则悉数删去。

图文解释 React Diff 算法

接下来咱们运用图文进行 React Diff 算法讲解,希望能够更进一步了解 React 的 Diff 算法。

最简略的 Diff 场景

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

上图的 Diff 场景是最简略的一种,新虚拟DOM 从左到右都能和老 Fiber 的节点逐个匹配成功,和谐方位的时分,老 Fiber A 的方位是 0,默许上一次和谐回来的方位也是 0,依据和谐方位规矩,老 Fiber 的方位不比上一次和谐回来的方位小,则只需求回来老 Fiber A 的方位 0 即可;到了 B 进行和谐方位的时分,老 Fiber B 方位 1 不比上一次和谐回来的方位 0 小,则只需回来老 Fiber B 的方位 1 即可;到了 C 进行和谐方位的时分,老 Fiber C 方位 2 不比上一次和谐回来的方位 1 小,则只需求回来老 Fiber C 的方位 2 即可;

终究悉数的新虚拟DOM 比对结束,但老 Fiber 上还存在节点信息,则需求将剩下的老 Fiber 进行删去符号。

接下来咱们看看杂乱的 Diff 场景。

杂乱的 Diff 场景

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

在上图中,第一轮循环比对的时分,新虚拟节点A 和第一个老 Fiber 节点是能够匹配的,所以就能够复用老 Fiber 的节点信息了,而且在和谐的方位信息的时分,是存在老 Fiber 的,那么就去比较老 Fiber 的方位和上一次和谐回来的方位进行比较(上一次和谐回来的方位默许为 0),老 Fiber 的方位是等于新 Fiber 的方位,依据和谐规矩,方位不需求移动,回来老 Fiber 的方位信息即可,很明显这次回来的和谐方位是 0。

到了第二个新虚拟节点 C 的时分,C 和老 Fiber 中的 B 是不匹配的,则第一轮比对结束。

第一轮比对结束之后,新虚拟DOM是还存在未比对的节点的,那么持续开端第二轮的比对。

在第二轮比对开端之前,会先将剩下未比对的老 Fiber 节点悉数处理成一个 老 Fiber 的 key 或老 Fiber 的 index 为 key,Fiber 节点为 value 的 Map 中。

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

然后进行第二轮的比对。

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

虚拟DOM C 能够经过 C 的 key 值在老 Fiber 的 Map 中找到老 Fiber C 节点,这个时分会 C 进行暂存,然后把 Map 中的 C 进行删去,再进行老 Fiber 的节点信息复用,然后去和谐比对方位信息。

老 Fiber C 的方位是 2,然后上一次新 Fiber A 和谐比对回来的方位信息是 0,那么这一次和谐的方位是老 Fiber 的方位比上一次和谐回来的方位大,那么这次和谐是不必符号 Placement 符号的,直接回来老 Fiber C 的方位 2。

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

虚拟DOM E,在老 Fiber 的 Map 中是没有匹配成功的,所以在创立 Fiber E 的时分,是没有进行老 Fiber 的复用的,去和谐比对方位的时分,依据和谐方位规矩,没有老 Fiber,就符号 Placement 并回来上一次和谐回来的方位,那么上一次 C 和谐方位回来的方位信息是 2,这一次 E 和谐方位仍然回来 2。

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

虚拟DOM B 也在 Fiber 的 Map 中匹配成功了,那么匹配成功之后,就对老 Fiber B 进行暂存,然后删去老 Fiber B,再进行信息复用,然后又进行方位和谐,老 Fiber B 的方位是 1,上一次和谐回来的方位是 2,依据和谐方位规矩,老 Fiber 的方位小于上一次和谐回来的方位,则符号 Placement 并回来上一次和谐回来的方位 2。

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

终究,老 Fiber 的 Map 中还存在一个 D 节点没处理,则需求对其进行删去符号操作。

终究新 Fiber 将被和谐成下面的姿态:

为什么 React 的 Diff 算法不选用 Vue 的双端比照算法?

那么依据图片,咱们又能够得出一个结论,匹配到的老 Fiber 假如和新 Fiber 相同或者在新 Fiber 方位的右边则不需求进行移动符号。

Vue3 的 Diff 算法

在我看来 Vue3 的 Diff 算法是 Vue2、Vue3、React 的 Diff 算法中最杂乱的一种。 下面咱们来简略说一下 Vue3 的 Diff 算法,只说数组和数组比对的状况。

第一轮,常见状况的比对

首要从左往右进行比对,假如是相同的就进行更新比对,假如不相同则中止比对,而且记载中止的下标。 再从右往左进行比对,假如是相同的就进行更新比对,假如不相同也中止比对,也进行记载中止的下标。 经过这样左右进行比对,终究就能够把真正杂乱部分进行规模确定了。 左右比对完之后,假如新节点现已比对完了,老节点列表还存在节点未比对,则删去老节点列表上的未比对的节点,假如老节点现已比对完了,新节点列表还存在未比对的节点则进行创立。

第二轮,杂乱状况的比对

假如新节点未比对完,老节点也未比对完,则进行终究最杂乱的处理。

先把剩下的新节点处理成节点的 key 为 key, 节点下标为 value 的 Map; 接着初始化一个长度为剩下未比对的新节点的长度的数组 newIndexToOldIndexMap,初始化每个数组的下标的默许值为 0。 再循环剩下的旧节点,经过旧节点的 key 去刚刚创立的 Map 中查找,看看旧节点有没有在新节点中,假如旧节点没有 key 则需求经过循环剩下的新节点进行查找。 假如旧节点在新节点中没找到,则阐明该旧节点需求进行删去。 假如找到了,则把找到的新节点的下标对应存储到上述的数组 newIndexToOldIndexMap 中,然后更新比对匹配到的新老节点。

把一切的旧节点比对完结后,就会得到一个刚刚搜集的新节点的下标数组,然后对这个新节点的下标数组进行进行最长递增子序列查找得到一个最长递增子序列的下标数据。 然后再进行循环左右比照完之后剩下新节点的下标,然后判别循环的下标是否被上述的数组 newIndexToOldIndexMap 进行搜集了,假如没被搜集到则阐明这个新节点需求进行创立,假如现已被搜集了则判别该循环的下标是否在上面核算得到的最长递增子序列中,假如不在则需求对该循环节点进行移动操作。

以上便是 Vue3 Diff 算法大约进程了。

愈加具体的 Vue3 Diff 算法解析能够查看我这篇文章:依据大崔哥的mini-vue来了解vue3中的diff算法

Vue2 的 Diff 算法

Vue2 的 Diff 算法便是以新的虚拟DOM为准进行与老虚拟DOM的比对,继而进行各种状况的处理。大约能够分为 4 种状况:更新节点、新增节点、删去节点、移动节点方位。比对新老两个虚拟DOM,便是经过循环,每循环到一个新节点,就去老节点列表里边找到和当时新节点相同的旧节点。假如在旧节点列表中找不到,阐明当时节点是需求新增的节点,咱们就需求进行创立节点并刺进视图的操作;假如找到了,就做更新操作;假如找到的旧节点与新节点方位不同,则需求移动节点等。

第一轮,简略状况的比对

其间为了快速查找到节点,Vue2 的 Diff 算法设置了 4 种优化战略,分别是:

  1. 老数组的开端与新数组的开端
  2. 老数组的结束与新数组的结束
  3. 老数组的开端与新数组的结束
  4. 老数组的结束与新数组的开端

经过这 4 种方便的查找方法,咱们就不需求循环来查找了,只要当以上 4 种方法都查找不到的时分,再进行循环查找。

第二轮,不常见的状况的比对

终究循环结束后需求对未处理的节点进行处理。

假如是老节点列表先循环结束,这个时分假如新节点列表还有剩下的节点,则阐明这些节点都是需求新增的节点,直接把这些节点创立并刺进到 DOM 中就行了。

假如是新节点列表先循环结束,这个时分假如老节点列表还有剩下节点,则阐明这些节点都是要被废弃的节点,是应该被删去的节点,直接批量删去就能够了。

愈加具体的 Vue2 diff 算法能够查看我这篇文章:Vue2 的 diff 算法详解

React、Vue3、Vue2 的 Diff 算法比照

相同点

只要运用了虚拟DOM的这些框架,在进行更新 Diff 比照的时分,都是优先处理简略的场景,再处理杂乱的场景。

React 中是先处理左面部分,左面部分处理不了,再进行杂乱部分的处理;Vue2 则先进行首尾、首首、尾尾部分的处理,然后再进行中心杂乱部分的处理;Vue3 则先处理首尾部分,然后再处理中心杂乱部分,Vue2 和 Vue3 最大的差异便是在处理中心杂乱部分运用了最长递增子序列算法找出安稳序列的部分。

在处理老节点部分,都需求把节点处理 key – value 的 Map 数据结构,方便在往后的比对中能够快速经过节点的 key 取到对应的节点。同样在比对两个新老节点是否相一起,key 是否相同也是十分重要的判别标准。所以不同是 React, 仍是 Vue,在写动态列表的时分,都需求设置一个仅有值 key,这样在 diff 算法处理的时分功能才最大化

在移动或者创立节点的时分都运用了 insertBefore(newnode,existingnode) 这个 API:

  1. newnode 必需。需求刺进的节点对象。
  2. existingnode 可选。在其之前刺进新节点的子节点。假如未规定,则 insertBefore 方法会在结束刺进 newnode。

不同点

对静态节点的处理不相同。

由于 Vue 是经过 template 模版进行编译的,所以在编译的时分能够很好对静态节点进行剖析然后进行打补丁符号,然后在 Diff 的时分,Vue2 是判别假如是静态节点则跳过过循环比照,而 Vue3 则是把整个静态节点进行提高处理,Diff 的时分是不过进入循环的,所以 Vue3 比 Vue2 的 Diff 功能更高效。而 React 由于是经过 JSX 进行编译的,是无法进行静态节点剖析的,所以 React 在对静态节点处理这一块是要逊色的。

Vue2 和 Vue3 的比对和更新是同步进行的,这个跟 React15 是相同的,便是在比对的进程中,假如发现了那些节点需求移动或者更新或删去,是当即履行的,也便是 React 中常讲的不可中止的更新,假如比对量过大的话,就会造成卡顿,所以 React16 起就更改为了比对和更新是异步进行的,所以 React16 以后的 Diff 是能够中止,Diff 和使命调度都是在内存中进行的,所以即便中止了,用户也不会知道。

别的 Vue2 和 Vue3 都运用了双端比照算法,而 React 的 Fiber 由于是单向链表的结构,所以在 React 不设置由右向左的链表之前,都无法完结双端比照。那么双端比照现在 React 的 Diff 算法要好吗?接下来咱们来看看一个比方,看看它分别在 React、Vue2、Vue3 中的是怎样处理的。

比方说咱们现在有以下两组新老节点:

老:A, B, C, D

新:D, A, B, C

那么咱们能够看到,新老两组节点仅有的不同点便是,D 节点在新的节点中跑到开头去了,像这种状况:

React 是从左向右进行比对的,在上述这种状况,React 需求把 A, B, C 三个节点分别移动到 D 节点的后边。

Vue2 在进行老节点的结束与新节点的开端比对的时分,就发现这两个节点是相同的,所以直接把老节点结束的 D 移动到新节点开头就行了,剩下的就只进行老节点的开端与新节点的开端进行比对,就能够发现它们的方位并没有产生改变,不需求进行移动。

Vue3 是没有了 Vue2 的新老首尾节点进行比较,仅仅从两组节点的开头和结束进行比较,然后往中心靠拢,那么 Vue3 在进行新老节点的开端和结束比对的时分,都没有比对成功,接下来就进行中心部分的比较,先把老节点处理成 key – value 的 Map 数据结构,然后又运用最长递增子序列算法找出其间的安稳序列部分,也便是:A, B, C,然再对新节点进行循环比对,然后就会发现新节点的 A, B, C 都在安稳序列部分,不需求进行移动,然就只对 D,进行移动即可。

终究上述的比方在 Vue2 和 Vue3 中都只需求移动一个节点就能够完结 Diff 算法比对,而 React 在这种极点比方中则没方法进行很好的优化,需求进行屡次节点移动操作。

为什么 Vue 中不需求运用 Fiber

其实这个问题也能够叫做:为什么 Vue 不需求时刻分片?关于这个问题其实尤雨溪也在英文社区里回答过,也有前端大牛翻译发布在公众号上,那么下面我也进行一下总结。

第一,首要时刻分片是为了处理 CPU 进行大量核算的问题,由于 React 本身架构的问题,在默许的状况下更新会进行过多的核算,就算运用 React 提供的功能优化 API,进行设置,也会由于开发者本身的问题,仍然或许存在过多核算的问题。

第二,而 Vue 经过响应式依靠跟踪,在默许的状况下能够做到只进行组件树级别的更新核算,而默许下 React 是做不到的(听说 React 现已在进行这方面的优化作业了),再者 Vue 是经过 template 进行编译的,能够在编译的时分进行十分好的功能优化,比方对静态节点进行静态节点提高的优化处理,而经过 JSX 进行编译的 React 是做不到的。

第三,React 为了处理更新的时分进行过多核算的问题引进了时刻分片,但一起又带来了额外的核算开支,便是使命和谐的核算,尽管 React 也运用最小堆等的算法进行优化,但相对 Vue 仍是多了额外的功能开支,由于 Vue 没有时刻分片,所以没有这方面的功能担忧。

第四,依据研讨标明,人类的肉眼对 100 毫秒以内的时刻并不灵敏,所以时刻分片只关于处理超过 100 毫秒以上的核算才有很好的收益,而 Vue 的更新核算是很少呈现 100 毫秒以上的核算的,所以 Vue 引进时刻分片的收益并不划算。

总结

咱们先由 “ React 的 Diff 算法为什么不选用 Vue 的双端比照的 Diff 算法?” 这个问题引出对 React 中的一些知识点的学习了解,比方什么是 Fiber,Fiber 链表是怎么生成的,然后具体解析了 React Diff 算法,还对 React Diff 算法进行图文并茂解析,让咱们能够愈加了解 React 的 Diff 算法。 其后,咱们又简略介绍了 Vue3 和 Vue2 的 Diff 算法,之后对 React、Vue3、Vue2之间的算法的异同进行了讲解。 终究咱们又总结了一下尤雨溪对 “为什么 Vue 不需求时刻分片?” 这个问题的解析。

我正在参与技术社区创作者签约方案招募活动,点击链接报名投稿。