diff算法浅析

本文深入探讨了在Vue和React中处理节点更新的Diff算法,包括无key值时采用的基本策略,以及有key值时的最大索引法、双端比较法和最长递增子序列法。详细解析了每种算法的执行流程和优化考虑,特别指出不要使用数组下标作为key,以避免不必要的DOM更新,影响性能。同时,文章强调了真实DOM操作的顺序应与新虚拟DOM节点顺序一致。
摘要由CSDN通过智能技术生成
  • 本文主要对最近学习的diff算法做个总结,会对node节点存在key时的diff算法和节点key不存在时的diff算法进行浅析,节点key存在时的最大索引法,双端比较法,最长递增子序列法进行逐一学习;会结合部分源码函数进行代码分析和执行分析;

1. 节点没有key值时所采用的算法;Vue3中在节点没有key的情况下采用的算法;

  • 1.1 遍历新旧节点数组中长度较小的节点数组,应用patch函数进行更新;
  • 1.2 对比新旧节点数组的长度,如果newNodeChild.length > oldNodeChild.length, 则说明有新节点需要添加;如果newNodeChild.length < oldNodeChild.length,则说明有旧节点需要移除;
  <!-- prevChildren:旧节点数组;nextChildren:新节点数组;-->
  // 获取公共长度,取新旧 children 长度较小的那一个
  const prevLen = prevChildren.length;
  const nextLen = nextChildren.length;
  const commonLength = prevLen > nextLen ? nextLen : prevLen;
  for (let i = 0; i < commonLength; i++) {
    patch(prevChildren[i], nextChildren[i], container);
  }
  // 如果 nextLen > prevLen,将多出来的新元素添加
  if (nextLen > prevLen) {
    for (let i = commonLength; i < nextLen; i++) {
      mount(nextChildren[i], container);
    }
  } else if (prevLen > nextLen) {
    // 如果 prevLen > nextLen,将多出来的旧元素移除
    for (let i = commonLength; i < prevLen; i++) {
      container.removeChild(prevChildren[i].el);
    }
  }

通过key属性,尽可能的复用已有的DOM元素,

注意:

  • 新增,删除,移动的操作都是对于真实dom来操作的;
  • 真实dom的移动顺序要和新vnode的顺序保持一致;
  • 不要用数组下标作为key值;否则对节点进行逆序增加,逆序删除等破坏结构顺序的操作时,会产生真实dom没必要的更新,影响渲染效率;当结构中存在输入类的dom时,可能会产生界面显示问题;
  • 不要用数组下标index拼接其他值作为key,这样会导致新节点中的每个key都不能在旧节点中找到可复用的节点,每个新节点都需要新建;

2. 节点key存在时的算法;

2.1. 最大索引法;通过节点key值尽可能多的复用DOM元素;React所采用的;

思路:先外层循环遍历新节点数组,内层循环遍历旧节点数组,根据新旧虚拟节点的key是否相等,判断该新节点是否可以复用旧节点;如果可复用的话是否需要移动以及怎样移动?(复用或新增)然后再循环旧节点数组,查找需要删除的旧节点;

1)遍历新节点数组,找到该新节点在旧节点数组中的下标,并记录下来,maxIndex默认取0;

  • 如果找到的下标是递增的,则说明新旧节点数组的顺序一致,无需移动;
  • 如果找到的下标 < 最大下标,即递减了,则说明该节点在旧节点数组中比较靠前,但是在新节点数组中靠后,则将该旧节点对应的真实dom移动到上一个新节点对应的真实dom后面;
  • 新增:如果该新节点在旧节点数组中未找到,则创建新dom元素,挂载新节点;

2)删除:遍历旧节点数组,判断该旧节点是否在新节点数组中存在,如果不存在,则删除该旧节点对应的真实dom;

    <!-- nextChildren:新节点数组;prevChildren:旧节点数组 -->
    let lastIndex = 0;  // 存放新节点在旧节点数组中的最大下标值
    // 以新节点数组为基准遍历
    for (let i = 0; i < nextChildren.length; i++) {
      const nextVNode = nextChildren[i];
      let j = 0,
        find = false;  // 标识该新节点是否在旧节点数组中存在;
      for (j; j < prevChildren.length; j++) {
        const prevVNode = prevChildren[j];
        if (nextVNode.key === prevVNode.key) {
          find = true; 
          patch(prevVNode, nextVNode, container);
          if (j < lastIndex) {
            // 需要移动,将旧节点对应的真实dom移动到上一个新节点对应的真实dom后面(即兄弟节点前面)
            const refNode = nextChildren[i - 1].el.nextSibling; // 上一个新节点真实dom的兄弟节点
            container.insertBefore(prevVNode.el, refNode);
            break;
          } else {
            // 更新 lastIndex
            lastIndex = j;
          }
        }
      }
      if (!find) {
        // 未在旧节点中找到该新节点,挂载新节点
        // 找到 refNode
        const refNode =
          i - 1 < 0 ? prevChildren[0].el : nextChildren[i - 1].el.nextSibling;
        mount(nextVNode, container, false, refNode);
      }
    }
    // 移除已经不存在的节点
    for (let i = 0; i < prevChildren.length; i++) {
      const prevVNode = prevChildren[i];
      // 拿着旧 VNode 去新 children 中寻找是否存在相同的节点
      const has = nextChildren.find((nextVNode) => nextVNode.key === prevVNode.key);
      if (!has) {
        // 如果没有找到相同的节点,则移除该旧节点对应的真实dom
        container.removeChild(prevVNode.el);
      }
    }

2.2.双端比较法; Vue2采用的算法;

  • 1)oldStartNode 和 newStartNode 对比;
  • 2)oldEndNode 和 newEndNode 对比;
  • 3)oldStartNode 和 newEndNode 对比;
  • 4)oldEndNode 和 newStartNode 对比;
  <!--
      Vue2中源码路径:Vue2\src\core\vdom\patch.js
  -->
  function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0     // 旧节点数组的开始下标
    let newStartIdx = 0     //新节点数组的开始下标
    let oldEndIdx = oldCh.length - 1    // 旧节点数组的结束下标
    let newEndIdx = newCh.length - 1    // 新节点数组的结束下标

    let oldStartVnode = oldCh[0]    // 旧节点数组的开始节点
    let oldEndVnode = oldCh[oldEndIdx]  // 旧节点数组的结束节点
    let newStartVnode = newCh[0]        // 新节点数组的开始节点
    let newEndVnode = newCh[newEndIdx]  // 新节点数组的结束节点

    /**
     * oldKeyToIdx: 对象,旧节点的map结构, {key:index}
     * idxInOld: number | undefined,该新节点如果在旧节点中存在时返回的存在下标;
     */
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(newCh)
    }
    /**
      注意:真实dom的移动顺序要和新vnode的顺序保持一致;
    */
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        // 处理前面循环在非理想情况下置为undefined的位置;
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        // 处理前面循环在非理想情况下置为undefined的位置;
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 旧start节点和新start节点相同,则vnode不移动
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 旧end节点和新end节点相同,则vnode不移动
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // 旧start节点和新end节点相同, 则将旧节点对应的真实dom移动到最后面(即最后一个旧节点的下一个兄弟节点前面),
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        // nodeOps.nextSibling(oldEndVnode.elm):返回旧end节点的下一个兄弟节点;
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        // 旧end节点和新start节点相同,则将旧end节点对应的真实dom移动到最前面(即旧start节点的最前面);
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // 非理想情况下;比较的节点不相同;
        // 则在旧节点中查找是否存在与新节点key值相同的节点,如果存在则将旧节点该位置的真实dom进行移动,并将该旧虚拟节点置为undefined;下一次循环式需跳过该位置
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)  // 构建map结构{key: node下标},方便查找,只在第一次构建
        // 根据newStart节点的key值在旧节点数组中查找是否存在相同key值的节点,如果存在将下标index记录下来;
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          // newStart节点不存在key属性,则直接用节点在旧数组中比对查找,
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { // New element
          // 说明newStart节点未在旧节点数组中找到,则新建节点
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          // 在旧节点数组中通过key找到了该newStart节点
          vnodeToMove = oldCh[idxInOld] // 新节点在旧节点数组中对应位置的节点
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // 相同的节点元素,只需移动
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            //将旧节点数组中对应位置处的vnode置为空,当oldStartIndex或oldEndIndex走到该位置时,跳过该节点的比对
            oldCh[idxInOld] = undefined    
            // 将找到的节点对应的真实dom节点移动到oldStart节点的前面
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // same key but different element. treat as new element;相同的键,但不同的元素。视为新元素
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    // 处理一个循环完,另一个还有剩余的边界情况;
    if (oldStartIdx > oldEndIdx) {
      // 当旧节点已经循环完毕,但是新节点中还存在没有被处理的全新节点,则直接将剩余的新节点逐个插入到oldStart前面;
      // 比如oldNode: [a, b, c],newNode:[d,a,b,c]的情况;
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      // 新节点先循环完毕,则将剩余旧节点对应的真实dom移除;
      // 比如oldNode:[a,b,c],newNode:[a,c]
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }

2.3. 最长递增子序列法,Vue3采用的算法;

  • 1 定义变量i,查找相同的前缀进行patch;
  • 2.定义变量e1(旧节点数组的长度-1),e2(新节点数组的长度 - 1),查找相同的后缀进行patch;
  • 3.patch完相同的前缀和后缀后,判断i,e1,e2之间的关系;如果i大于e1则说明旧节点数组中所有节点都已经参与patch了,但是新节点数组中的节点还存在为patch的节点,即有节点需要新建;i>e2时,说明有旧节点需要移除;
  • 4.如果新旧节点数组中都存在未patch的节点,则通过构建未参与patch的新节点数组的最长递增子序列,查找需要移动的节点以及判断节点怎样移动;查找需要创建的新节点进行新建与挂载;

参考博客:
vue3.0 diff算法详解(超详细)

源码路径:packages\runtime-core\src\renderer.ts
// can be all-keyed or mixed;节点存在key时的diff比对算法
  const patchKeyedChildren = (
    c1: VNode[],
    c2: VNodeArrayChildren,
    container: RendererElement,
    parentAnchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
  
    let i = 0
    const l2 = c2.length
    let e1 = c1.length - 1 // prev ending index
    let e2 = l2 - 1 // next ending index
    
    // 1. sync from start;查找相同的前缀,只需一个变量即可
    // (a b) c
    // (a b) d e
    while (i <= e1 && i <= e2) {
      const n1 = c1[i]
      const n2 = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
      if (isSameVNodeType(n1, n2)) {
        // n1和n2的节点类型和key值都相同;
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      } else {
        // 一发现节点不相同,则立即退出循环
        break
      }
      i++
    }
    
    // 2. sync from end;查找相同的后缀,由于新旧节点数组的长度不一定相同,所以需要两个变量
    // a (b c)
    // d e (b c)
    while (i <= e1 && i <= e2) {
      const n1 = c1[e1]
      const n2 = (c2[e2] = optimized
        ? cloneIfMounted(c2[e2] as VNode)
        : normalizeVNode(c2[e2]))
      if (isSameVNodeType(n1, n2)) {
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      } else {
        break
      }
      e1--
      e2--
    }
   
    // 3. common sequence + mount;公共序列+挂载
    // (a b)
    // (a b) c
    // i = 2, e1 = 1, e2 = 2;
    // (a b)
    // c (a b)
    // i = 0, e1 = -1, e2 = 0
    if (i > e1) {
      // 说明旧节点数组中的所有节点已经参与前两步的patch了
      if (i <= e2) {
        // 但是新节点数组中还有剩余节点未参与
        const nextPos = e2 + 1
        const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i <= e2) {
          // 将新节点数组中未参与比较的节点逐个进行新建挂载;
          // 未参与的节点下标范围:[i, e2]
          patch(
            null,
            (c2[i] = optimized
              ? cloneIfMounted(c2[i] as VNode)
              : normalizeVNode(c2[i])),
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG
          )
          i++
        }
      }
    }
    
    // 4. common sequence + unmount;公共序列+移除
    // (a b) c
    // (a b)
    // i = 2, e1 = 2, e2 = 1
    // a (b c)
    // (b c)
    // i = 0, e1 = 0, e2 = -1
    else if (i > e2) {
      // 说明新节点数组中的所有节点已经参与前两步的patch了
      while (i <= e1) {
        // 但是旧节点数组中还有剩余;则逐个移除未参与的旧节点;未参与的旧节点范围:[i, e1]
        unmount(c1[i], parentComponent, parentSuspense, true)
        i++
      }
    }
    
    // 5. unknown sequence;未知序列;i既不大于e1也不大于e2;即新旧节点数组中都还有未参与patch的节点,
    // 用最长递增子序列查找需要移动的节点以及怎样移动?
    // [i ... e1 + 1]: a b [c d e] f g
    // [i ... e2 + 1]: a b [e d c h] f g
    // i = 2, e1 = 4, e2 = 5
    // 下面的代码以prevChild=[a,b,c,d,e,f,g],nextChild=[a,b,e,d,c,h,f,g]例子带入理解 
    else {
      const s1 = i // prev starting index
      const s2 = i // next starting index
    
      // 5.1 build key:index map for newChildren;
      // 构建未参与的剩余新节点的map结构,方便查找;map的key为该节点的key值,value为该节点在新节点数组中的下标
      const keyToNewIndexMap: Map<string | number, number> = new Map()
      for (i = s2; i <= e2; i++) {
        const nextChild = (c2[i] = optimized
          ? cloneIfMounted(c2[i] as VNode)
          : normalizeVNode(c2[i]))
        if (nextChild.key != null) {
          if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
            warn(
              `Duplicate keys found during update:`,
              JSON.stringify(nextChild.key),
              `Make sure keys are unique.`
            )
          }
          keyToNewIndexMap.set(nextChild.key, i)
        }
      }
    
      // 5.2 loop through old children left to be patched and try to patch
      // matching nodes & remove nodes that are no longer present(匹配节点&删除不再存在的节点)
      let j
      let patched = 0   // 记录已patch的节点数量,当patched > toBePatched时,则说明已经patch完毕,多余节点需要移除;
      const toBePatched = e2 - s2 + 1 // 新节点数组中剩余未patch的节点个数
      let moved = false   // 记录节点是否需要移动
      // used to track whether any node has moved(用于跟踪是否有任何节点已移动)
      let maxNewIndexSoFar = 0  // 最大索引法
      // works as Map<newIndex, oldIndex>
      // Note that oldIndex is offset by +1
      // and oldIndex = 0 is a special value indicating the new node has
      // no corresponding old node.
      // used for determining longest stable subsequence
      // 初始化长度为toBePatched的数组,并将数组元素初始化为0,存放未参与patch的新节点在未参与patch的旧节点数组中的下标
      const newIndexToOldIndexMap = new Array(toBePatched)
      for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
    
      for (i = s1; i <= e1; i++) {
        // 遍历未参与patch的旧节点数组,
        const prevChild = c1[i]
        if (patched >= toBePatched) {
          // 已更新的节点数 > 剩余未patch的节点数,则卸载旧节点
          // all new children have been patched so this can only be a removal
          unmount(prevChild, parentComponent, parentSuspense, true)
          continue
        }
        let newIndex  // 存放相同key的新节点在新节点数组中的下标
        if (prevChild.key != null) {
          // 旧节点存在key
          newIndex = keyToNewIndexMap.get(prevChild.key)    // 用旧节点的key在新节点对应的map中查找是否存在该key值的节点,存在则返回其所在下标
        } else {
          // key-less node, try to locate a key-less node of the same type(无密钥节点,尝试定位同一类型的无密钥节点)
          for (j = s2; j <= e2; j++) {
            // 在未参与patch的剩余新节点中查找是否有相同的节点
            if (
              newIndexToOldIndexMap[j - s2] === 0 &&
              isSameVNodeType(prevChild, c2[j] as VNode)
            ) {
              // 如果找到有新节点对应,则记录该节点在新节点数组中的下标
              newIndex = j
              break
            }
          }
        }
        if (newIndex === undefined) {
          // 未找到,则说明该旧节点对应的真实dom需要被移除
          unmount(prevChild, parentComponent, parentSuspense, true)
        } else {
          // 找到了;更新newIndexToOldIndexMap数组中该节点在旧节点数组中的下标值;
          newIndexToOldIndexMap[newIndex - s2] = i + 1
          if (newIndex >= maxNewIndexSoFar) {
            maxNewIndexSoFar = newIndex
          } else {
            moved = true
          }
          patch(
            prevChild,
            c2[newIndex] as VNode,
            container,
            null,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
          patched++
        }
      }
      // 5.3 move and mount
      // generate longest stable subsequence only when nodes have moved
      // 如果需要移动,则返回最长递增子序列的下标数组;
      // 如newIndexToOldIndexMap=[5,4,3,0],则返回[2],表示最长递增子序列是由下标为2的元素构成;
      const increasingNewIndexSequence = moved
        ? getSequence(newIndexToOldIndexMap)
        : EMPTY_ARR   // [2]
      j = increasingNewIndexSequence.length - 1   // 0
      // looping backwards so that we can use last patched node as anchor
      // i = toBePatched - 1时,即c2[e2]节点
      for (i = toBePatched - 1; i >= 0; i--) {    // i: 3,2,1,0
        // 有多少新节点未被patch
        const nextIndex = s2 + i    // 新节点的下标
        const nextChild = c2[nextIndex] as VNode  // 新节点,即 h,c,d,e
        //  l2 = c2.length,即新节点数组的长度,则新节点的下标为[0, l2-1]
        const anchor =
          nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
        if (newIndexToOldIndexMap[i] === 0) {
          // 此新节点在旧节点中不存在,需要新建挂载
          // mount new
          patch(
            null,
            nextChild,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG
          )
        } else if (moved) {
          // 此新节点在旧节点中存在,且需要移动
          // move if:
          // There is no stable subsequence (e.g. a reverse)
          // OR current node is not among the stable sequence
          // 比较的是下标;
          if (j < 0 || i !== increasingNewIndexSequence[j]) {
            move(nextChild, container, anchor, MoveType.REORDER)
          } else {
            j--
          }
        }
      }
    }
  }

处理未知序列的示例:以 prevChild: [a b c d e f g],nextChild: [a b e d c h f g]为例,分析Vue3的diff算法执行;

  • 已知条件:c1 = prevChild; c2 = nextChild; i=0;l2=c2.length = 8; l1 = c1.length=7; e1 = 6;e2 = 7

  • 1.使用变量i, patch新旧节点数组中相同前缀的节点,如果发现节点不同则退出循环,记录此时变量i的值;
    相同前缀节点:[a,b];i=2;

  • 2.使用变量e1,e2,patch新旧节点数组中相同后缀的节点,如果发现节点不同则退出循环,记录此时e1,e2变量的值;
    相同后缀节点:[f,g],此时e1 = 4;e2=5;

  • 3.判断变量i是否大于e1并且小于e2(即旧节点数组中的所有节点都已经参与patch了,说明新节点数组中[i,e2]范围的节点需要新建),

  • 4.判断变量i是否大于e2并且小于e1,(即新节点数组中的所有节点都已经参与patch了,旧节点数组中[i,e1]范围的节点需要被移除);
    此时 i<e1 && i<e2;即新旧节点数组中都还有未参与patch的节点;

  • 5.处理未知序列,即i既不大于e1也不大于e2的情况;
    s1 = s2 = i = 2

  • 5.1 构建未参与patch的剩余新节点的map结构;
    keyToNewIndexMap = {e:2,d:3,c:4,h:4}

  • 5.2 构建剩余未处理的新节点在旧节点数组中的下标;根据下标判断是否有旧节点需要移除;是否有旧节点需要移动?   剩余未处理的新节点个数:toBePatched = e2 - s2 +1 = 4;    剩余未处理的新节点在旧节点数组中的下标:newIndexToOldIndexMap初始为[0,0,0,0];

  • 遍历剩余未处理的旧节点[i,e1]范围的节点,判断剩余未处理的新节点在旧节点数组中的下标,从而更新newIndexToOldIndexMap数组;更新后的值为[5,4,3,0];     newIndex:与旧节点的key相同的新节点在keyToNewIndexMap的键值;
    代码执行过程如下:
    在这里插入图片描述
    最大索引法判断是否有节点需要移动:maxNewIndexSoFar:未参与的新节点在旧节点数组中的最大下标;

  • 5.3 根据newIndexToOldIndexMap数组获取最长递增子序列的下标数组;最长递增子序列的算法可以参考leetcode第300题;    increasingNewIndexSequence = [2],表示最长递增子序列是由下标为2的元素构成;   j = increasingNewIndexSequence.length-1 = 0

  • 5.4 从后往前遍历未patch的新节点;对新增的节点进行新建挂载;如果moved=true,则表明有旧节点需要移动,则对旧节点进行适当的移动操作;
    image.png
    如果文中有错误欢迎大家指正,我们一起学习一起进步,谢谢啦;

全文参考资料:霍春阳大佬的Vue源码逐行代码分析
vue3.0 diff算法详解(超详细)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wen_文文

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值