虚拟DOM和diff算法(五) 命中算法,精细化更新updateElement

971 阅读4分钟

四种命中方式

  1. 新前与旧前
  2. 新后与旧后
  3. 新后与旧前(命中后-移动节点,移动[新前]指向的[旧节点],移动的[旧后之后])
  4. 新前与旧后(命中后-移动节点,移动[新前]指向的[旧节点],移动的[旧前之前])

  • 命中一点就不再进行命中判断了,如果没命中选择其他方案。
  • 如果都没命中,则应该用循环,再旧节点里找,移动位置。
  • 如果是旧节点先循环完毕,说明新节点中 有要插入的节点。
  • 如果是新节点先循环完毕,如果老节点中 还有剩余节点,说明他们是要被删除的节点。

具体方法

1.声明4个指针

新前,新后,旧前,旧后

2.四个指针指向的节点 3.while循环处理

新前 小于 新后 && 旧前 小于 旧后

4.判断指[针节]点是否未 null 或是 undefined 则跳过节点 5.判断是[四种]命中,是否命中

检查是否未同一个节点(同样的key) 更新节点patchVnode 移动指针

6.[四种]命中都没有命中,制作keyMap一个映射对象,这样就不用每次都遍历老对象了。

找到新前节点(新前在循环中,不断更新)的key,在旧节点中的key 如果是undefined 则证明他是全新项目,插入到全部未处理的最前面 如果不是undefined 则不是全新的项目,则需要移动节点

7.循环结束后,判断start和old的大小

新前小于新后,则遍历newCh,添加到没处理之前 旧前小于旧后,则旧节点未处理完,则要删除旧节点

import createElement from "./createElement.js"
import patchVnode from "./patchVnode.js"
// 新旧节点是否是同一个节点
function checkSameVnode(a, b) {
  console.log(a,b, '----------------')
  return a.sel === b.sel && a.key === b.key
};
export default function updateElement(parentEle, oldCh, newCh) {
  // 旧前
  let oldStartInd = 0;
  // 新前
  let newStartInd = 0;
  // 旧后
  let oldEndInd = oldCh.length - 1;
  // 新后
  let newEndInd = newCh.length - 1;
  // 旧前节点
  let oldStartVnode = oldCh[0];
  // 旧后节点
  let oldEndVnode = oldCh[oldEndInd];
  // 新前节点
  let newStartVnode = newCh[0];
  // 新后节点
  let newEndVnode = newCh[newEndInd];
  // 存储未被命中的key 
  let keyMap = null;
  // while循环处理
  // 新前 小于 新后 && 旧前 小于 旧后
  while (newStartInd <= newEndInd && oldStartInd <= oldEndInd) {
    console.log('★')
    if (oldStartVnode == null || oldStartVnode == undefined) {
      
      oldStartVnode = oldCh[++oldStartInd]
    } else if (oldEndInd == null || oldEndInd == undefined) {
      
      oldEndVnode = oldCh[--oldEndInd]
    } else if (newStartInd == null || newStartInd == undefined) {
      
      newStartVnode = newCh[++newStartInd]
    } else if (newEndInd == null || newEndInd == undefined) {
      
      newEndVnode = newCh[--newEndInd]
    } else if (checkSameVnode(oldStartVnode, newStartVnode)) {
      
      // 检查是否为同一个节点
      console.log('1-------------')
      // 新前和旧前
      console.log('①新前和旧前命中');
      patchVnode(oldStartVnode, newStartVnode);
      oldStartVnode = oldCh[++oldStartInd]; // 指针先后移,再取值
      newStartVnode = newCh[++newStartInd]; // 指针先后移,再取值
    } else if (checkSameVnode(oldEndVnode, newEndVnode)) {
      
      console.log('2-------------')
      // 新后与旧后
      console.log('②新后和旧后命中');
      patchVnode(oldEndVnode, newEndVnode);
      oldEndVnode = oldCh[--oldEndInd]; // 指针先前移,再取值
      newEndVnode = newCh[--newEndInd]; // 指针先前移,再取值
    } else if (checkSameVnode(oldStartVnode, newEndVnode)) {
      
      console.log('3-------------')
      // 新后与旧前
      console.log('③新后与旧前(此种发生了,涉及移动节点,那么新前指向的节点,移动的旧后之后)');
      // patchVnode(oldStartVnode, newEndVnode);
      /**
       * 当③新后与旧前命中的时候,此
       * 时要移动节点。移动新前指向的
      *  这个节点到老节点的旧后的后面
       */
       // 如何移动节点??只要你插入一个已经在DOM树上的节点,它就会被移动
       parentEle.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
      oldStartVnode = oldCh[++oldStartInd]; // 指针先后移,再取值
      newEndVnode = newCh[--newEndInd];  // 指针先前移,再取值
      
    } else if (checkSameVnode(oldEndVnode, newStartVnode)) {
      
      // 新前与旧后
      console.log('④新前与旧后(此种发生了,涉及移动节点,那么新前指向的节点,移动的旧前之前)');
      patchVnode(oldEndVnode, newStartVnode);
      /**
       * 当④新前与旧后命中的时候,此
时要移动节点。移动新前指向的
这个节点到老节点的旧前的前面
       */
      parentEle.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
       // 如何移动节点??只要你插入一个已经在DOM树上的节点,它就会被移动
      oldEndVnode = oldCh[--oldStartInd]; // 指针先前移,再取值
      newStartVnode = newCh[++newStartInd]; // 指针先后移,再取值
    } else {
      
      // 全部未命中
      // 寻找新的keyMap
      if (!keyMap) {
        
        keyMap = {}
        // 缓存旧节点中的key
        for (let i = oldStartInd; i <= oldEndInd; i++) {
          const key = oldCh[i].key;
          // 不等于undefined,则标识未处理
          if (key != undefined) {
            keyMap[key] = i;
          }
        }
      }
      console.log(keyMap);
      // 寻找当前这项(newStartIdx)这项在keyMap中的映射的位置序号
      const indInOld = keyMap[newStartVnode.key]
      console.log(indInOld)
      if (indInOld == undefined) {
        // 判断,如果idxInOld是undefined表示它是全新的项
        // 被加入的项(就是newStartVnode这项)现不是真正的DOM节点
        parentEle.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
      } else {
        // 如果不是undefined,不是全新的项,而是要移动
        const elmToMove = oldCh[indInOld]
        patchVnode(elmToMove, newStartVnode);
        // 把这项设置为undefined,表示我已经处理完这项了
        oldCh[indInOld] = undefined;
        // 移动,调用insertBefore也可以实现移动。
        parentEle.insertBefore(elmToMove.elm, oldStartVnode.elm)
      }
      // 指针下移,只移动新的头
      newStartVnode = newCh[++newStartInd];
    }
   
  }
   // 继续判断是否有剩余几点,循环结束了start还是比old小
  if (newStartInd <= newEndInd) {
    console.log('new还有剩余节点没有处理,要加项。要把所有剩余的节点,都要插入到oldStartIdx之前');
     // 遍历新的newCh,添加到老的没有处理的之前
    for (let i = newStartInd; i <= newEndInd; i++) {
      // insertBefore方法可以自动识别null,如果是null就会自动排到队尾去。和appendChild是一致了。
      // newCh[i]现在还没有真正的DOM,所以要调用createElement()函数变为DOM
      parentEle.insertBefore(createElement(newCh[i]), oldCh[oldStartInd].elm)
    }
  } else if (oldStartInd <= oldEndInd) {
    console.log('old还有剩余节点没有处理,要删除项');
    for (let i = oldStartInd; i <= oldEndInd; i++) {
      if (oldCh[i]) {
        parentEle.removeChild(oldCh[i].elm)
      }
    }
  }
}