vue3 diff算法

diff算法的流程

1、从头对比找到有相同的节点 patch ,发现不同,立即跳出。

2、如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环。

3、如果新的节点大于老的节点数 ,对于剩下的节点全部以新的vnode处理( 这种情况说明已经patch完相同的vnode )。

4、对于老的节点大于新的节点的情况 , 对于超出的节点全部卸载 ( 这种情况说明已经patch完相同的vnode )。

5、不确定的元素( 这种情况说明没有patch完相同的vnode ) 与 3 ,4对立关系。

把没有比较过的新的vnode节点,通过map保存

记录已经patch的新节点的数量 patched

没有经过 path 新的节点的数量 toBePatched

建立一个数组newIndexToOldIndexMap,每个子元素的初始值都是[ 0, 0, 0, 0, 0, 0, ] 里面的数字记录老节点的索引 ,数组索引就是新节点的索引, 数组索引是从0开始的

开始遍历老节点

① 如果 toBePatched新的节点数量为0 ,那么统一卸载老的节点

② 如果,老节点的key存在 ,通过key找到对应的index

③ 如果,老节点的key不存在

1 遍历剩下的所有新节点

2 如果找到与当前老节点对应的新节点,那么将新节点的索引,赋值给newIndex

④ 没有找到与老节点对应的新节点,卸载当前老节点。

⑤ 如果找到与老节点对应的新节点,把老节点的索引,记录在存放新节点的数组中,

1 如果节点发生移动 记录已经移动了

2 patch新老节点 找到新的节点进行patch节点

遍历结束

如果发生移动

① 根据 newIndexToOldIndexMap 新老节点索引列表找到最长稳定序列

② 对于 newIndexToOldIndexMap -item =0 证明不存在老节点 ,从新形成新的vnode

③ 对于发生移动的节点进行移动处理。

diff源码解析

js 复制代码
// can be all-keyed or mixed
  const patchKeyedChildren = (
    c1: VNode[],
    c2: VNodeArrayChildren,
    container: RendererElement,
    parentAnchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    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)) {
         // 相同类型节点, 执行更新操作
         // patch -> patchElememt
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          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,
          slotScopeIds,
          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) {
    // 新的子节点列表比旧的子节点列表长,添加多余的节点
      if (i <= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i <= e2) {
          patch(
            null,
            (c2[i] = optimized
              ? cloneIfMounted(c2[i] as VNode)
              : normalizeVNode(c2[i])),
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          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) {
    // 新的子节点列表比旧的子节点列表短,移除多余的节点
      while (i <= e1) {
        unmount(c1[i], parentComponent, parentSuspense, true)
        i++
      }
    }

    // 5. unknown sequence
    // [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
    else {
    // 对比更新子节点列表中间部分的差异
      const s1 = i // prev starting index
      const s2 = i // next starting index

      // 5.1 build key:index map for newChildren
      // keyToNewIndexMap的key是新节点的key, value是新节点的位置/索引
      const keyToNewIndexMap: Map<string | number | symbol, 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
      const toBePatched = e2 - s2 + 1 
      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
      // newIndexToOldIndexMap是一个用于建立新子节点在旧子节点列表中的索引映射的数组, 它的索引表示新子节点列表的索引, 而数组中的值表示该子节点在旧子节点列表中的索引
      // 这个数组的创建是为了在后续的循环中构建`newIndexToOldIndexMap`,以便在对比和更新中间部分差异的时候,能够快速地找到新子节点在旧子节点列表中的对应节点。
      const newIndexToOldIndexMap = new Array(toBePatched)
      for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0

      for (i = s1; i <= e1; i++) { // 遍历旧节点中间差异数组
        const prevChild = c1[i]
        if (patched >= toBePatched) {
          // all new children have been patched so this can only be a removal
          unmount(prevChild, parentComponent, parentSuspense, true)
          continue
        }
        let newIndex
        if (prevChild.key != null) { // 如果旧节点key存在
         // keyToNewIndexMap对象中key是新节点的key,value是该key所在的位置
          newIndex = keyToNewIndexMap.get(prevChild.key) // 通过相同key确认旧节点在新节点列表中的位置
        } else {
          //如果旧节点没有key
          // key-less node, try to locate a key-less node of the same type
          for (j = s2; j <= e2; j++) { //遍历新节点数组
            if (
              newIndexToOldIndexMap[j - s2] === 0 &&
              isSameVNodeType(prevChild, c2[j] as VNode) 
            ) {
            // 在新节点列表遍历找到旧节点相同的节点
              newIndex = j //通过isSameVNodeType确认旧节点在新节点列表中的位置
              break
            }
          }
        }
        if (newIndex === undefined) {
        // 在新节点列表中找不到旧节点,则删除该旧节点
          unmount(prevChild, parentComponent, parentSuspense, true)
        } else {
          
          // i是节点在旧列表中的索引, newIndex是节点在新列表中的索引
          // newIndexToOldIndexMap 是一个数组
          // i在s1到e1之间, newIndex在s2到e2之间
          // 如:[s1,...el] = [2,3,4,5,6] = [20,30,40,50,60]
          // 如果[20,30,40,50,60]变成[2,3,4,5,6]=>[40,50,20,30,60]
          // i=2; newIndex=4; s2=2  newIndexToOldIndexMap=[2] = 3
          // i=3; newIndex=5 newIndexToOldIndexMap=[3] = 4
          // i=4; newIndex=2 newIndexToOldIndexMap=[0] = 5
          // i=5; newIndex=3 newIndexToOldIndexMap=[1] = 6
          // i=6; newIndex=6 newIndexToOldIndexMap=[4] = 7
          // newIndexToOldIndexMap = [5,6,3,4,7]
         // 这里为什么是i + 1,是为了防止i=0的情况,当i=0时,如果不加1,newIndexToOldIndexMap这一项的值就为0,而newIndexToOldIndexMap的初始值就为0,代表新增的节点,
          newIndexToOldIndexMap[newIndex - s2] = i + 1
          if (newIndex >= maxNewIndexSoFar) {
            maxNewIndexSoFar = newIndex
          } else {
            moved = true
          }
          // 对比新旧节点的差异,更新旧节点
          patch(
            prevChild,
            c2[newIndex] as VNode,
            container,
            null,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          patched++
        }
      }

      // 5.3 move and mount
      // generate longest stable subsequence only when nodes have moved
      // getSequence获取最长递增子序列
      const increasingNewIndexSequence = moved
        ? getSequence(newIndexToOldIndexMap)
        : EMPTY_ARR
      j = increasingNewIndexSequence.length - 1
      // looping backwards so that we can use last patched node as anchor
      for (i = toBePatched - 1; i >= 0; i--) {
        const nextIndex = s2 + i
        const nextChild = c2[nextIndex] as VNode
        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,
            slotScopeIds,
            optimized
          )
        } 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--
          }
        }
      }
    }
  }
js 复制代码
const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
// 将旧节点的el(即真实DOM)赋值给 新节点的el
    const el = n2.el = n1.el;
    ```
    if (patchFlag & 1) {
        if (n1.children !== n2.children) {
          hostSetElementText(el, n2.children);
        }
      }
     ```
}
js 复制代码
// 将新节点上的新的内容赋值给旧节点对应的真实DOM上;
// 此时新节点上的el和旧节点上的el是同一对象, 
  setElementText: (el, text) => {
    el.textContent = text;
  },
js 复制代码
const move = (vnode, container, anchor, moveType, parentSuspense = null) => {
    const { el, type, transition, children, shapeFlag } = vnode;
    if(){
    ````
    } else {
      // 将50移动到20前面会走该分支
      hostInsert(el, container, anchor);
    }
  };
js 复制代码
// insertBefore 会完成节点的移动
// insertBefore方法在参考节点的前面插入一个拥有指定父节点的子节点,如果给定的子节点是对文档中现有节点的引用,insertBefore()会将其从当前位置移动到新位置
const nodeOps = {
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null);
  },
}
相关推荐
黑客老陈28 分钟前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安33 分钟前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy1 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se1 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235611 小时前
web 渗透学习指南——初学者防入狱篇
前端
z千鑫1 小时前
【前端】入门指南:Vue中使用Node.js进行数据库CRUD操作的详细步骤
前端·vue.js·node.js
m0_748250742 小时前
Web入门常用标签、属性、属性值
前端
m0_748230442 小时前
SSE(Server-Sent Events)返回n ,前端接收数据时被错误的截断【如何避免SSE消息中的换行符或回车符被解释为事件消息的结束】
前端