手写vue-diff算法(三)updateChildren

前文回顾

上一篇提到,新老儿子节点比对可能存在的 3 种情况及对应的处理方法:

  • 情况 1:老的有儿子,新的没有儿子

处理方法:直接将多余的老dom元素删除即可;

  • 情况 2:老的没有儿子,新的有儿子

处理方法:直接将新的儿子节点放入对应的老节点中即可;

  • 情况 3:新老都有儿子

处理方法:执行diff比对,即:乱序比对;

针对情况 3 新老儿子节点的比对,采用了“头尾双指针”的方法

优先对新老儿子节点的“头头、尾尾、头尾、尾头”节点进行比对,若均未能命中,最后再执行乱序比对;

diff算法-都有儿子节点

1.头头对比-新序列比老序列多节点-尾部

老:A B C D
新:A B C D E

1.1基本准备

在这里插入图片描述

function updateChildren(el, oldChildren, newChildren) {
    // 我们操作列表 经常会是有  push shift pop unshift reverse sort这些方法  (针对这些情况做一个优化)
    // vue2中采用双指针的方式 比较两个节点
    let oldStartIndex = 0;
    let newStartIndex = 0;
    let oldEndIndex = oldChildren.length - 1;
    let newEndIndex = newChildren.length - 1;

    let oldStartVnode = oldChildren[0];
    let newStartVnode = newChildren[0];

    let oldEndVnode = oldChildren[oldEndIndex];
    let newEndVnode = newChildren[newEndIndex];
}

1.2 循环分析

oldStartIndex > oldEndIndex说明是新的删除了部分节点
newStartIndex > newEndIndex说明是新的增加了部分节点
当不满足以上两种情况,进行头头、尾尾、头尾、尾头、乱序对比

while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {}

1.3顺序对比

当新的头节点和旧的头节点相同时,调用patchVnode方法对子节点进行对比,同时进行属性更新,此处已经开始了递归调用
接下来将新旧头节点向后移动继续对比

while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        if (isSameVnode(oldStartVnode, newStartVnode)) {
            patchVnode(oldStartVnode, newStartVnode);
            oldStartVnode = oldChildren[++oldStartIndex]
            newStartVnode = newChildren[++newStartIndex]
        }
}

1.4 新的添加了部分节点

可能增加了好多个节点
el:旧的子节点序列
使用createElm创建新的虚拟节点,插入到el

if (newStartIndex <= newEndIndex) {
        // push
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            let childEL = createElm(newChildren[i])
            el.appendChild(childEL)
        }
    }

2.尾尾对比-新序列比老序列少节点-尾部

老:A B C D E
新:A B C D
el调用删除子节点方法removeChild,循环进行删除

if (oldStartIndex <= oldEndIndex) {
        // pop
        for (let i = oldStartIndex; i <= oldEndIndex; i++) {
            let childEL = oldChildren[i].el
            el.removeChild(childEL)
        }
    }

3.头头对比-新序列比老序列多节点-头部

老:A B C D
新:E A B C D

从尾部进行对比,然后新旧尾节点逐渐减小

else if (isSameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode);
            oldEndVnode = oldChildren[--oldEndIndex]
            newEndVnode = newChildren[--newEndIndex]
        }

此时插入方法需要进行修改,旧的插入方法时插入到尾部,此处需要插入到头部。

  • 如何判断是向前追加还是向后追加?
    向后追加尾指针后面没有节点,向前追加尾指针后面有节点,并且是插入到尾指针节点的前面
    if (newStartIndex <= newEndIndex) {
        // push
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            let childEL = createElm(newChildren[i])
            let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null; // 获取下一个元素
            // el.appendChild(childEL)
            el.insertBefore(childEL, anchor); 
        }
    }

4.尾尾对比-新序列比老序列少节点-尾部

老:A B C D
新:A B C D E
同2

5.交叉对比-老序列头部和新序列尾部相同

老:D E A B C
新:A B C D

老节点序列头部和新节点序列尾部相同
对比这两个节点,将老序列头部节点后移,新序列尾部节点向前移动

else if (isSameVnode(oldStartVnode, newEndVnode)) {
            patchVNode(oldEndVnode, newEndVnode);
            el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);
            oldStartVnode = oldChildren[++oldStartIndex]
            newEndVnode = newChildren[--newEndIndex]
        }

6.交叉对比-老序列尾部和新序列头部相同

老:A B C D
新:D A B C

老节点序列尾部和新节点序列头部相同
在老序列节点头部插入新序列尾部节点
对比这两个节点,将老序列尾部节点前移,新序列头部节点向后移动

 else if (isSameVnode(oldEndVnode, newStartVnode)) {
            patchVNode(oldEndVnode, newStartVnode);
            el.insertBefore(oldEndVnode.el, oldStartVnode.el);
            oldEndVnode = oldChildren[--oldEndIndex]
            newStartVnode = newChildren[++newStartIndex]
        }

7.乱序对比

老:A B C D
新:B P A C D Q N
当新老序列不满足上面的六种情况后,以老节点序列作为对比序列,从新节点序列里面挨个拿出节点进行对比
B在老节点中有,则把B插入到oldStartVnode之前,并将B原来的位置标识为undefined,新指针指向P,老序列头指针不变,为A
P没有,则将P插入到oldStartVnode之前,新指针指向A,老序列头指针不变,为A
此时新老头指针相同,情况1,新老头指针向后移动,

    function makeIndexByKey(children) {
        let map = {

        }
        children.forEach((child, index) => {
            map[child.key] = index;
        });
        return map;
    }

    let map = makeIndexByKey(oldChildren);
else {
            // 在给动态列表添加key的时候 要尽量避免用索引,因为索引前后都是从0 开始 , 可能会发生错误复用 
            // 乱序比对
            // 根据老的列表做一个映射关系 ,用新的去找,找到则移动,找不到则添加,最后多余的就删除
            let moveIndex = map[newStartVnode.key]; // 如果拿到则说明是我要移动的索引
            if (moveIndex !== undefined) {
                let moveVnode = oldChildren[moveIndex]; // 找到对应的虚拟节点 复用
                el.insertBefore(moveVnode.el, oldStartVnode.el);
                oldChildren[moveIndex] = undefined; // 表示这个节点已经移动走了
                patchVnode(moveVnode, newStartVnode); // 比对属性和子节点
            } else {
                el.insertBefore(createElm(newStartVnode), oldStartVnode.el);
            }
            newStartVnode = newChildren[++newStartIndex];
        }

code

function updateChildren(el, oldChildren, newChildren) {
    // 我们操作列表 经常会是有  push shift pop unshift reverse sort这些方法  (针对这些情况做一个优化)
    // vue2中采用双指针的方式 比较两个节点
    let oldStartIndex = 0;
    let newStartIndex = 0;
    let oldEndIndex = oldChildren.length - 1;
    let newEndIndex = newChildren.length - 1;

    let oldStartVnode = oldChildren[0];
    let newStartVnode = newChildren[0];

    let oldEndVnode = oldChildren[oldEndIndex];
    let newEndVnode = newChildren[newEndIndex];


    function makeIndexByKey(children) {
        let map = {

        }
        children.forEach((child, index) => {
            map[child.key] = index;
        });
        return map;
    }

    let map = makeIndexByKey(oldChildren);




    // 循环的时候为什么要+key
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) { // 有任何一个不满足则停止  || 有一个为true 就继续走
        // 双方有一方头指针,大于尾部指针则停止循环
        if (!oldStartVnode) {
            oldStartVnode = oldChildren[++oldStartIndex]
        } else if (!oldEndVnode) {
            oldEndVnode = oldChildren[--oldEndIndex]
        } else if (isSameVnode(oldStartVnode, newStartVnode)) {
            patchVnode(oldStartVnode, newStartVnode); // 如果是相同节点 则递归比较子节点
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = newChildren[++newStartIndex];
            // 比较开头节点
        } else if (isSameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode); // 如果是相同节点 则递归比较子节点
            oldEndVnode = oldChildren[--oldEndIndex];
            newEndVnode = newChildren[--newEndIndex];
            // 比较开头节点
        } else if (isSameVnode(oldEndVnode, newStartVnode)) {
            patchVnode(oldEndVnode, newStartVnode);
            // insertBefore 具备移动性 会将原来的元素移动走
            el.insertBefore(oldEndVnode.el, oldStartVnode.el); // 将老的尾巴移动到老的前面去
            oldEndVnode = oldChildren[--oldEndIndex];
            newStartVnode = newChildren[++newStartIndex];
        }
        else if (isSameVnode(oldStartVnode, newEndVnode)) {
            patchVnode(oldStartVnode, newEndVnode);
            // insertBefore 具备移动性 会将原来的元素移动走
            el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling); // 将老的尾巴移动到老的前面去
            oldStartVnode = oldChildren[++oldStartIndex];
            newEndVnode = newChildren[--newEndIndex];
        } else {
            // 在给动态列表添加key的时候 要尽量避免用索引,因为索引前后都是从0 开始 , 可能会发生错误复用 
            // 乱序比对
            // 根据老的列表做一个映射关系 ,用新的去找,找到则移动,找不到则添加,最后多余的就删除
            let moveIndex = map[newStartVnode.key]; // 如果拿到则说明是我要移动的索引
            if (moveIndex !== undefined) {
                let moveVnode = oldChildren[moveIndex]; // 找到对应的虚拟节点 复用
                el.insertBefore(moveVnode.el, oldStartVnode.el);
                oldChildren[moveIndex] = undefined; // 表示这个节点已经移动走了
                patchVnode(moveVnode, newStartVnode); // 比对属性和子节点
            } else {
                el.insertBefore(createElm(newStartVnode), oldStartVnode.el);
            }
            newStartVnode = newChildren[++newStartIndex];
        }

    }
    if (newStartIndex <= newEndIndex) { // 新的多了 多余的就插入进去
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            let childEl = createElm(newChildren[i])
            // 这里可能是像后追加 ,还有可能是向前追加
            let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null; // 获取下一个元素
            // el.appendChild(childEl);
            el.insertBefore(childEl, anchor); // anchor 为null的时候则会认为是appendChild
        }
    }

    if (oldStartIndex <= oldEndIndex) { // 老的对了,需要删除老的
        for (let i = oldStartIndex; i <= oldEndIndex; i++) {
            if (oldChildren[i]) {
                let childEl = oldChildren[i].el
                el.removeChild(childEl);
            }
        }
    }

    // 我们为了 比较两个儿子的时候 ,增高性能 我们会有一些优化手段
    // 如果批量像页面中修改出入内容 浏览器会自动优化 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vue-code-diff是一个用于在Vue项目中显示代码差异的插件。当vue-code-diff不对齐时,可能有以下几个原因和解决方法: 1. 插件版本不兼容:首先要确保vue-code-diff插件的版本与Vue项目的版本兼容。可以通过查看vue-code-diff的文档或者安装最新版本的插件来解决这个问题。 2. 样式冲突:vue-code-diff的展示需要一定的样式支持,可能存在与项目中其他样式库或自定义样式发生冲突的情况。可以通过使用scoped样式、更改样式命名空间或重新设计样式来解决这个问题。 3. 代码格式问题:如果代码在Vue组件中使用了不同的缩进或对齐方式,vue-code-diff可能会出现不对齐的情况。在这种情况下,需统一代码的缩进和对齐方式,确保比对的代码结构一致。 4. 数据源问题:vue-code-diff的对比结果是基于提供的数据源进行展示的。如果数据源不正确或者解析出现问题,将导致代码不对齐。检查数据源的正确性,并确保数据用于生成代码差异的函数正确返回对应的数据。 5. 其他问题:如果以上方法都没有解决vue-code-diff不对齐的问题,那可能是插件本身的bug或者其他未知的原因。可以尝试在插件的GitHub页面上提交issue,或者尝试寻找其他的代码对比插件替代解决。 总之,对于vue-code-diff不对齐的问题,需要根据具体情况进行分析和排查,找出具体原因并采取相应的解决方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值