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


``
相关推荐
mldong2 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen7 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒8 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端