手写简化版的 vue2 diff 算法

521 阅读1分钟
  • 简化了 Vue2 diff 的过程,主要专注于子节点之间的 diff,下面是一个手写的 diff 算法,跟源码有细微的差别,主要为了理解。
const insertBefore = (ele, refer, nodes) => {
    const target = nodes.findIndex(e => e === ele);
    if (~target) {
        nodes.splice(target, 1);
    }
    nodes.splice(nodes.findIndex(e => e === refer), 0, ele);
}

const insertAfter = (ele, refer, nodes) => {
    const target = nodes.findIndex(e => e === ele);
    if (~target) {
        nodes.splice(target, 1);
    }
    nodes.splice(nodes.findIndex(e => e === refer) + 1, 0, ele);
}

// const makeIndexMap = (nodes, startIndex, endIndex) => {
//   let ret = {};
//   for (let i = startIndex; i <= endIndex; i++) {
//     ret[nodes[i]] = i;
//   }
//   return ret;
// }

const oldNodes = ['e', 'd', 'g', 'j', 'b', 'c', 'a'];
const newNodes = ['g', 'c'];

// 四个指针,分别指向新老节点的头尾
function vue2Diff(oldNodes, newNodes) {
    let oldStartIndex = 0, newStartIndex = 0;
    // let oldNodesIndex;
    let [oldEndIndex, newEndIndex] = [oldNodes.length - 1, newNodes.length - 1];

    // 主要是先通过新节点去对比旧节点,从而去操作老节点的移动。
    // 假如其中有一方已经遍历完了,那么就是表明可单个元素的 diff 已经完成,接下来就是进入批量删除或者批量添加的过程
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        let [oldStartNode, newStartNode] = [oldNodes[oldStartIndex], newNodes[newStartIndex]];
        let [oldEndNode, newEndNode] = [oldNodes[oldEndIndex], newNodes[newEndIndex]];

        // 假如新老节点的首位是相同的,那么新老指针同时向后移动
        if (oldStartNode === newStartNode) {
            oldStartIndex++;
            newStartIndex++;
        }
        // 假如新老节点的末位是相同的,那么新老指针同时向前移动
        else if (oldEndNode === newEndNode) {
            oldEndIndex--;
            newEndIndex--;
        }
          // 假如老节点的首位等于新节点的末尾,那么将老节点的首位,移动到 oldEndIndex 后面
        // 同时老节点的指针前移,新节点的指针后移
        else if (oldStartNode === newEndNode) {
            insertAfter(oldStartNode, oldEndNode, oldNodes)
            oldEndIndex--;
            newStartIndex++;
        }
          // 假如老节点的末位等于新节点的首位,那么将老节点的末位结束移动到 oldStartIndex 前面
        // 同时将新老节点的指针一起前移
        else if (oldEndNode === newStartNode) {
            insertBefore(oldEndNode, oldStartNode,  oldNodes);
            oldStartIndex++;
            newStartIndex++;
        } else {
            // 源代码里面是将新节点的 key 对应 下标生成一个对象,方便新节点找到其在老节点中的下标
            // if (!oldNodesIndex) oldNodesIndex = makeIndexMap(oldNodes, oldStartIndex, oldEndIndex);
            // 这里简化了操作,直接获取其在老节点中的下标
            const newInOldIndex = oldNodes.indexOf(newStartNode);
            // 假如新节点在老节点里面,那么就将找到的老的节点移动到 newStartIndex 的前面,为什么要放在 newStartIndex 前面
            // 同时新老指针前移
            if (~newInOldIndex) {
                insertBefore(oldNodes[newInOldIndex], oldStartNode, oldNodes);
                oldStartIndex++;
                newStartIndex++;
                //
            }
              // 假如新节点不在老节点里面,那么就将找到的老的节点移动到 newStartIndex 的前面,为什么要放在 newStartIndex 前面
            // 同时新老指针前移
            else {
                insertBefore(newStartNode, oldStartNode, oldNodes);
                oldStartIndex++;
                oldEndIndex ++;
                newStartIndex++;
            }
        }
    }

    // 假如到最后还有多余的元素,那么就批量添加或批量删除
    if (oldEndIndex - oldStartIndex >= 0) {
        oldNodes.splice(oldStartIndex, oldEndIndex - oldStartIndex + 1);
    } else {
        oldNodes.splice(oldStartIndex, 0,  ...newNodes.slice(newStartIndex, newEndIndex + 1));
    }
}

vue2Diff(oldNodes, newNodes);