从项目中再看vue diff 算法

2023-08-08  本文已影响0人  冷暖自知_zjz

页面如下

diff1.jpg diff1.jpg diff2.jpg diff2.jpg

代码如下

code1.png code1.png code2.jpg code2.jpg

问题:当点击HelloWorld 的改变isShow的值从false变为true

会打印HelloWorld-mounted 也就是说HelloWorld组件的mouted 执行了,也就是HelloWorld组件销毁重建了。从代码看HelloWorld组件上没有v-if 所以不是业务代码控制的是否销毁,那么还有那种情况呢。对,就是vue-diff算法对比过程中销毁重建了。从代码上看。是指在代码的收尾各加了一个div。为什么会出现HelloWorld组件销毁重建呢?

duandian1.jpg duandian1.jpg

从上图中可以看出newCh 新的节点和oldCh老的节点都是长度为4的虚拟节点数组。diff 的时候会两头两尾,头尾、尾头两两对比。都不满足后会走到updateChildren 的while 里面的else 从老的节点数组中查找是否有和newStartVnode相同的节点sameVnode 所以找到了 老的里面的第二个和新的里面的第一个是相同节点。

duandian2.jpg duandian2.jpg

在oldCh数组里面索引为1的也就是第二个就是 HelloWorld 组件会被销毁

再次while 时 如下图

duandian3.jpg duandian3.jpg

是拿老的里面的索引为2的和新的里面索引为1的被判断为相同的。newCh中的 HelloWorld 组件会被创建 所以HelloWorld组件的mounted 再次执行

vue-diff.png vue-diff.png

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
// 虚拟节点中所包含的属性
export default class VNode {
tag: string | void; // 标签名
data: VNodeData | void; // 数据
children: ?Array; // 子节点数组
text: string | void;  // 真实节点所对应的文本
elm: Node | void;  // 对应的真实dom
key: string | number | void; // 元素上定义的key
parent: VNode | void; // component placeholder node
isStatic: boolean; // hoisted static node  // 是否是静态节点
isComment: boolean; // 是否是注释节点
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?  // 是否是v-once的节点
}
// 更新子节点
function updateChildren (parentElm, oldCh, newCh) {
    // 定义两个指针分别指向两个子节点数组的开始
    let oldStartIdx = 0
    let newStartIdx = 0
    // 分别获取新老子节点数组的长度和四个开始和结束节点
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm
    // 当新老节点都没有对比完一直循环
    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)
        // 移动两个指针
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      }
      // 同上
      else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      }
      // 首尾比较
      else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode)
        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)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      }
      // 使用v-for 建议添加key 当两首两尾首尾都不值得比较,这个时候如果定义了key就会通过key查找是否值得比较
      else {
        if (isUndef(oldKeyToIdx)) 
            // createKeyToOldIdx会返回一个map key就是老元素的key值是老元素的索引(oldKeyToIdx)
            oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        // findIdxInOld方法 (在老的里面查找出值得比较的元素)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        // idxInOld (老子节点数组中值得比较的节点的索引位置)
        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)
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // key 相同但是元素不同,按新元素处理
            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)
    } else if (newStartIdx > newEndIdx) {
      //老的子节点数组长度长 删除多余的
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }
上一篇 下一篇

猜你喜欢

热点阅读