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));


``
相关推荐
迷迭绿8 小时前
Cesium 轨迹回放播放器
前端·javascript·cesium
HolySaga9 小时前
我用 UTS 写了一个三端同构的蓝牙插件(Android / iOS / 鸿蒙 NEXT),把踩过的坑都讲给你听
前端
程序员老赵9 小时前
Docker 部署 XWiki:轻松搭建企业知识库与协作 Wiki
前端·docker·开源
console.log('npc')9 小时前
第 10 章 更多业务模块扩展
前端·人工智能·大模型
行者全栈架构师9 小时前
我用 TRAE Work 把桌游之夜搬进了浏览器:一个计分器的从创意到上线
前端·算法·架构
山雀~9 小时前
线上CI流水打包失败排查思路案例
前端·ci/cd
时间的情敌9 小时前
《回顾 2019 前端:React Hooks 问世,前端工程化爆发的一年》
前端·react.js·前端框架
燕落南枝10 小时前
CSS 类选择器详解:从入门到实战
前端·css
tryCbest10 小时前
Vue3——第 7 篇:组件基础——从注册到使用的完整流程
前端·javascript·vue.js
ai工具集合10 小时前
图标集用法,前端项目 SVG 图标 icon 完整接入实操
前端