vue diff算法与虚拟dom知识整理(12) patch精细化比较新增子节点

上文中我们编写了patch函数中对相同节点的几种处理 将简单的都写完了 但还留下了最麻烦的子节点比较
既新旧节点都有子节点 需要 精细化比较

我们先将src下的入口文件index.js 代码改成这样

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c"),
  h("p", {
    
    key:"d"}, "d")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

新节点就比老节点多一个子节点
我们这边更新子节点 也分三种情况

其实处理我们这个最后多一个节点的逻辑是会比较简单的 因为他的节点是 以新节点为基础 一个一个节点去遍历的
判断如果有 就好 没有就加上

而我们key的作用在于 如果 第一个子节点 新旧节点都是li 一个内容是 c 一个内容是 a 如果你两个节点都没有key 那么 他就会确认到 key和标签名都相同 因为 标签名都是li key都没设置 就是undefined 那么他就会判断为 是同一个节点 只是文本不一样 这样 他就会将你的 a 替换文本成c
但你如果 设置了key 他就不会乱动你的东西 而是认真判断

然后 我们就可以来写这块最复杂的代码了 当新旧节点都有子节点
在这里插入图片描述
我们先编写代码如下

//当新旧节点都有子节点
//遍历写节点的子元素
newVnode.children.map(news => {
    
    
    //定义 isExist  判断新节点中有没有和旧节点一样的元素
    let isExist = false;
    //遍历旧节点子集
    oldVnode.children.map(old => {
    
    
        //判断  如果有一模一样的元素  什么都不用做
        if(old.sel == news.sel &&old.key == news.key) {
    
    
            isExist = true;
        }
    })
    //判断  如果在旧节点中没有找到相同的节点 就输出出来
    if(!isExist){
    
    
        console.log(news);
    }
})

这里 我们先遍历新节点的所有子节点 然后遍历旧节点的所有子节点 对每一个新节点进行判断 找找看 旧节点的子节点中有没有和当前节点相同的 如果没有 我们就输出出来
然后 我们运行项目
在这里插入图片描述
然后 我们点击更改dom
在这里插入图片描述
这里 key为d的节点被输出了
我们看到index.js
在这里插入图片描述
我们可以很明显的看到 确实是只有 d 在旧节点中找不到相同的元素

然后 我们可以进而将代码改写成这样

//当新旧节点都有子节点
//定义un用于记录当前更新的元素下标
let un = 0;
//遍历写节点的子元素
newVnode.children.map((news,index) => {
    
    
    //定义 isExist  判断新节点中有没有和旧节点一样的元素
    let isExist = false;
    //遍历旧节点子集
    oldVnode.children.map(old => {
    
    
        //判断  如果有一模一样的元素  什么都不用做
        if(old.sel == news.sel &&old.key == news.key) {
    
    
            isExist = true;
        }
    })
    //判断  如果在旧节点中没有找到相同的节点 就输出出来
    if(!isExist){
    
    
        //通过createElement将虚拟节点变成真正的孤儿节点
        let dom = createElement(news);
        //当当前下标的子节点的elm属性记录上这个新创建的孤儿节点
        news.elm = dom;
        //判断un是不是已经大于了子节点创的   如果是 表示这个下标在老节点找不到
        if(un < oldVnode.children.length) {
    
    
            //在老节点对应un下标的前面插入这个节点
            oldVnode.elm.insertBefore(dom, oldVnode.children[un].elm);
        }else{
    
    
            //直接在老节点的最后面插入
            oldVnode.elm.appendChild(dom);
        }
    }else{
    
    
        //否则就表示 本次循环中 子元素找到了相同的元素 将un+1
        un++;
    }
})

我们通过un记录下标 然后 我们循环遍历新节点 在新节点的变量中定义isExist 这个用来记录当前这个节点有没有相同的

然后遍历老节点 在老节点的遍历中 找有没有与新节点子元素相同
例如 我们新节点 是 a b c d 老节点是 a b c 那我 新节点遍历子节点 第一次进来的是 a 他就会循环遍历老节点的子节点 第一次 就 新节点的a和老节点下面的a是一样的
达到条件之后就会复制isExist 为ture记录
这个大家不用担心 第二次 b进循环 isExist 声明的这个代码就会创新执行 他还是初始值 false
然后 走出老节点的循环 还在新节点的循环中 还是那a节点举例 我们判断isExist
如果他不是 false 表示 新节点和老节点都有这个元素 我们就将un加一 用于记录 这个节点已经被记录了

那么 不同的只有新节点的d了
d进来 因为找不到一样 的 到最后判断isExist 就还是false
然后 我们还是通过createElement 将这个虚拟节点 变成一个真实的孤儿节点
然后 然后 news记录的就是d本身 因为 他是我们循环新节点子节点的下标 就是当前循环的元素
我们直接将新创建的孤儿节点存到当前虚拟节点的elm中
然后判断
un是不是大于旧节点的子节点数量

如果不大于 直接插入在新节点un下标的子节点的前面
例如 如果
旧节点是 a b c
新节点是 a d b c
这样 第一次 进来 a和a相同
isExist 是true un++
现在un是1
然后 第二次进来 d 找不到相同的 就会 执行到判断 un是不是大于旧节点的数量
旧节点有三个子元素 un是1 显然un小于旧节点的子集
那就执行
oldVnode.elm.insertBefore(dom, oldVnode.children[un].elm);
通过就节点的elm拿到旧节点的真实dom节点作为父级调用insertBefore 将用d虚拟节点创建的孤儿节点插入在旧节点的 un下标的节点前面 那么 旧节点的 1 下标 就是 b节点 因为下标是从零开始的
那么 旧节点此时就变成了 adbc
按我现在的路径走到 判断un时 他是大于旧节点的子集的
那么 直接就用appendChild 将他插入在 旧节点的elm的最后面
我们运行项目
在这里插入图片描述
点击更新dom
在这里插入图片描述
我们的d 就上去了

在这里插入图片描述
我们现在来换个顺序
index.js 代码修改如下

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"d"}, "d"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

这就是我们上面的举例
我们运行项目
在这里插入图片描述
点击 更改dom
在这里插入图片描述
也是没有任何问题

那么 问题来了 如果是交换顺序呢?

我们更改 index.js 代码如下

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"c"}, "c"),
  h("p", {
    
    key:"b"}, "b")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

我们将 b和c的顺序换一下
但我们现在点击是没有任何效果的
在这里插入图片描述
因为 我们只考虑了元素新增 还没有处理老节点的调整
下文 我们继续来处理一下 老节点的节点变化

猜你喜欢

转载自blog.csdn.net/weixin_45966674/article/details/130855321