vue2 中diff算法和 vue3中diff算法对比

前言

对于源码的阅读应该是建立在实际需要的过程中,这样才是科学的学习方式,不然一味地全盘阅读,先不说能不能读得完,这过程也是非常痛苦和枯燥。最近在应用虚拟列表的过程中发现vue3的更新方式给我造成了困惑,遂重温下vue2的diff算法,顺便学习下vue3的diff算法

vue2的diff算法

说一下主要流程

① 判断是否是同一个node节点

  1. 对比key
  2. 对比tag
  3. 对比comment
  4. 对比是否同一个Input、
  5. 对比placeholder

② patch

遵循一个基本原则:

先优先处理好处理的边界情况,最后全量递归对比

处理边界情况

  1. 新节点存在文本节点的情况下,当旧节点的子节点和新节点的子节点都存在的情况下,开始diff算法(也就是③),等会讲。
  2. 当旧节点没有子节点的情况下,检查新节点子节点的key的重复性.如果旧节点存在文本节点,则清空文本节点。然后批量将新节点的子节点添加到elem后面
  3. 如果旧节点存在子节点,则移除所以的子节点。
  4. 如果新节点没有子节点但是旧节点有子节点,则清空旧节点的文本内容
  5. 若新旧节点文本内容不同则替换。

③ 具体diff算法(全量递归对比)

js 复制代码
    // 递归全量对比
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(
          oldStartVnode,
          newStartVnode,
          insertedVnodeQueue,
          newCh,
          newStartIdx
        )
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(
          oldEndVnode,
          newEndVnode,
          insertedVnodeQueue,
          newCh,
          newEndIdx
        )
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // Vnode moved right
        patchVnode(
          oldStartVnode,
          newEndVnode,
          insertedVnodeQueue,
          newCh,
          newEndIdx
        )
        canMove &&
          nodeOps.insertBefore(
            parentElm,
            oldStartVnode.elm,
            nodeOps.nextSibling(oldEndVnode.elm)
          )
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // Vnode moved left
        patchVnode(
          oldEndVnode,
          newStartVnode,
          insertedVnodeQueue,
          newCh,
          newStartIdx
        )
        canMove &&
          nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        if (isUndef(oldKeyToIdx))
          oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) {
          // New element
          createElm(
            newStartVnode,
            insertedVnodeQueue,
            parentElm,
            oldStartVnode.elm,
            false,
            newCh,
            newStartIdx
          )
        } else {
          vnodeToMove = oldCh[idxInOld]
          if (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(
              vnodeToMove,
              newStartVnode,
              insertedVnodeQueue,
              newCh,
              newStartIdx
            )
            oldCh[idxInOld] = undefined
            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) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(
        parentElm,
        refElm,
        newCh,
        newStartIdx,
        newEndIdx,
        insertedVnodeQueue
      )
    } 
    //新节点长度小于旧节点,删除旧节点多余的部分
    else if (newStartIdx > newEndIdx) {
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }

下面用图示表示下vue2 diff的过程

下面图示表示下vue3 diff的过程

这里说下最定长稳定序列,因为大部分情况下,页面都是静态或者没有变化的元素,因此,这部分操作无疑是无效对比,浪费性能。新旧vVode列表相同的部分就是最长稳定序列。对比vue2的全量对比,提高了性能。

最长稳定序列(递增)用算法举例如下:

数列 1, 2, 3, 4, 5, 3, 2, 1, 4, 9的最长稳定(递增)序列如下;

1, 2,3,4,5

附一道最长递增序列算法

js 复制代码
const list1 = [1, 2, 3, 4, 5, 3, 2, 8, 9, 10, 11, 12, 13, 14, 15, 3, 2, 1, 23];
const list2 = [1, 2, 3, 4, 5, 3, 2, 8];

function findMaxIncreaseList(list) {
  let start = 0;
  let end = 1;
  let tempList = [list[start]];
  let rltList = [];
  while (end < list.length) {
    if (list[end] > list[start]) {
      tempList.push(list[end]);
    } else {
      start = end - 1;
      tempList = [list[end]];
    }
    if (rltList.length < tempList.length) {
      rltList = tempList.concat();
    }
    end++;
    start++;
  }
  return rltList;
}
console.log(findMaxIncreaseList(list1));
console.log(findMaxIncreaseList(list2));


``
相关推荐
随风一样自由2 小时前
【前端独角兽】刚进入前端领域的前端开发用jQuery还是Vue3?
前端·javascript·vue3·jquery
IMPYLH2 小时前
HTML 的 <data> 元素
前端·html
YHHLAI3 小时前
[特殊字符] 面试中的 Promise —— 从入门到精通
前端·javascript·面试
leoZ2314 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
kyriewen5 小时前
我扒了 GPT-5.6 的全部编程跑分——有一项 OpenAI 没敢放出来
前端·gpt·ai编程
前端H5 小时前
微前端 v3 架构升级:从加载隔离到状态协同
前端·架构·状态模式
陆枫Larry6 小时前
小程序包体积优化:用 SVG 图片替换 iconfont,并保留 CSS 控色能力
前端
环境栈笔记7 小时前
多账号浏览器选型检查清单:Profile、权限、Session 和任务日志怎么评估
前端·人工智能·后端·自动化
前端炒粉8 小时前
手写promise
java·前端·javascript
LaughingZhu8 小时前
智能体经典范式构建:ReAct、Plan-and-Solve 与 Reflection
前端·react.js·前端框架