首页 > 编程语言 >Vue源码学习(十五):diff算法(二)交叉比对(双指针)

Vue源码学习(十五):diff算法(二)交叉比对(双指针)

时间:2023-11-07 15:33:08浏览次数:57  
标签:el Vue oldChildren Vnode oldVnode let newChildren diff 源码

好家伙,

 

本节来解决我们上一章留下来的问题,

新旧节点同时有儿子的情况本章继续解决

 

1.要做什么?

本章将解决,

1.在相同tag下子元素的替换问题

2.使用双指针进行元素替换,

实现效果如下:

 

    let vm1 = new Vue({data:{name:'张三'}})
    let render1 = compileToFunction(`<ul>
    <li style="background:yellow" key="c">我是黄色</li>
    </ul>`)
    let vnode1 = render1.call(vm1)
     document.body.appendChild(createELm(vnode1))

   //数据更新
     let vm2 = new Vue({data:{name:'李四'}})
     let render2 = compileToFunction(`<ul>
     <li style="background:blue" key="c">我是蓝色</li>
    </ul>`)
     let vnode2 = render2.call(vm2)

     //patch 比对
      setTimeout(()=>{
        patch(vnode1,vnode2)
      },2000)

 

2.思路

let vm1 = new Vue({
  data: {
    name: '张三'
  }
})
let render1 = compileToFunction(`<ul>
    <li style="background:red" key="a">a</li>
     <li style="background:pink" key="b">b</li>
     <li style="background:blue" key="c">c</li>
    </ul>`)
let vnode1 = render1.call(vm1)
document.body.appendChild(createELm(vnode1))

//数据更新
let vm2 = new Vue({
  data: {
    name: '李四'
  }
})
let render2 = compileToFunction(`<ul>
     <li style="background:red" key="a">a</li>
     <li style="background:pink" key="b">b</li>
     <li style="background:blue" key="c">c</li>
     <li style="background:yellow" key="d">d</li>
    </ul>`)
let vnode2 = render2.call(vm2)

setTimeout(() => {
  patch(vnode1, vnode2)
}, 2000)

我们用这个例子来举例

 

1.正序(从头开始)

 

 

 

找到不同(原先没有的)的项,再将它添加上去

大概的思路就是如此.

 

但同时,根据不同的情况

我们还有多种比对方法

2.2.逆序

 

2.3.交叉对比(从头)

 

2.4.交叉对比(从尾)

 

 

 

3.代码实现

3.1.双指针

//双指针 遍历
    let oldStartIndex = 0 //老的开头索引
    let oldStartVnode = oldChildren[oldStartIndex];
    let oldEndIndex = oldChildren.length - 1
    let oldEndVnode = oldChildren[oldEndIndex]

    let newStartIndex = 0 //新的开头索引
    let newStartVnode = newChildren[newStartIndex];
    let newEndIndex = newChildren.length - 1
    let newEndVnode = newChildren[newEndIndex]

双指针的写法非常粗暴,但是好用

 

3.2.循环

(照着上面的图看)

while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        //比对子元素
        console.log(666)
        if (isSomeVnode(oldStartVnode, newStartVnode)) {
            //递归
            debugger;
            //1 从头部开始
            patch(oldStartVnode, newStartVnode);
            //移动指针
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = oldChildren[++newStartIndex];
        }//2 从尾部开始
        else if(isSomeVnode(oldEndVnode, newEndVnode)){
            //
            patch(oldEndVnode, newEndVnode);
            oldEndVnode = oldChildren[--oldEndIndex]
            newEndVnode = newChildren[--newEndIndex]
        }//3 交叉比对 从头
        else if(isSomeVnode(oldStartVnode,newEndVnode)){
            patch(oldStartVnode, newEndVnode);
            oldStartVnode =oldChildren[++oldStartIndex]
            newEndVnode = newChildren[--newEndIndex];
        }//4 交叉比对 从尾
        else if(isSomeVnode(oldEndVnode,newStartVnode)){
            patch(oldEndVnode, newStartVnode);
            oldEndVnode =oldChildren[--oldStartIndex]
            newStartVnode = newChildren[++newStartIndex];
        }
    }

 

3.3.isSomeVnode()

isSomeVnode()方法用于判断两个节点是否相同

function isSomeVnode(oldContext, newContext) {
    // return true
    return (oldContext.tag == newContext.tag) && (oldContext.key === newContext.key);
}  

 

3.4.添加多余的子儿子

//判断完毕,添加多余的子儿子  例子:旧的a b c  新的 a b c d  将d添加到parent
    if (newStartIndex <= newEndIndex) {
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            
            parent.appendChild(createELm(newChildren[i]))
        }
    }

 

搞定

 

4.patch.js完整代码

如下:

export function patch(oldVnode, Vnode) {
    debugger;

    //原则  将虚拟节点转换成真实的节点
    // console.log(oldVnode, Vnode)
    // console.log(oldVnode.nodeType)
    // console.log(Vnode.nodeType)
    //第一次渲染 oldVnode 是一个真实的DOM
    //判断ldVnode.nodeType是否为一,意思就是判断oldVnode是否为属性节点
    if (oldVnode.nodeType === 1) {
        console.log(oldVnode, Vnode) //注意oldVnode 需要在加载 mount 添加上去  vm.$el= el

        let el = createELm(Vnode) // 产生一个新的DOM
        let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
        //   console.log(oldVnode)
        //  console.log(parentElm)

        parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
        parentElm.removeChild(oldVnode) //删除老节点
        //重新赋值
        return el
    } else { //  diff
        // console.log(oldVnode.nodeType)
        console.log(oldVnode, Vnode)
        //1 元素不是一样 
        if (oldVnode.tag !== Vnode.tag) {
            //旧的元素 直接替换为新的元素
            return oldVnode.el.parentNode.replaceChild(createELm(Vnode), oldVnode.el)
        }
        //2 标签一样 text  属性 <div>1</div>  <div>2</div>  tag:undefined
        if (!oldVnode.tag) {
            if (oldVnode.text !== Vnode.text) {
                return oldVnode.el.textContent = Vnode.text
            }
        }
        //2.1属性 (标签一样)  <div id='a'>1</div>  <div style>2</div>
        //在updataRpors方法中处理
        //方法 1直接复制
        let el = Vnode.el = oldVnode.el
        updataRpors(Vnode, oldVnode.data)
        //diff子元素 <div>1</div>  <div></div>
        let oldChildren = oldVnode.children || []
        let newChildren = Vnode.children || []
        if (oldChildren.length > 0 && newChildren.length > 0) { //老的有儿子 新有儿子
            //创建方法
            
            updataChild(oldChildren, newChildren, el)
        } else if (oldChildren.length > 0 && newChildren.length <= 0) { //老的元素 有儿子 新的没有儿子
            el.innerHTML = ''
        } else if (newChildren.length > 0 && oldChildren.length <= 0) { //老没有儿子  新的有儿子
            for (let i = 0; i < newChildren.length; i++) {
                let child = newChildren[i]
                //添加到真实DOM
                el.appendChild(createELm(child))
            }
        }

    }
}

function updataChild(oldChildren, newChildren, parent) {
    //diff算法 做了很多优化 例子<div>11</div> 更新为 <div>22</div> 
    //dom中操作元素 常用的 思想 尾部添加 头部添加 倒叙和正序的方式

    //双指针 遍历
    let oldStartIndex = 0 //老的开头索引
    let oldStartVnode = oldChildren[oldStartIndex];
    let oldEndIndex = oldChildren.length - 1
    let oldEndVnode = oldChildren[oldEndIndex]

    let newStartIndex = 0 //新的开头索引
    let newStartVnode = newChildren[newStartIndex];
    let newEndIndex = newChildren.length - 1
    let newEndVnode = newChildren[newEndIndex]
    console.log(oldEndIndex,newEndIndex)
    console.log(oldEndVnode,newEndVnode)
    
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        //比对子元素
        console.log(666)
        if (isSomeVnode(oldStartVnode, newStartVnode)) {
            //递归
            debugger;
            //1 从头部开始
            patch(oldStartVnode, newStartVnode);
            //移动指针
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = oldChildren[++newStartIndex];
        }//2 从尾部开始
        else if(isSomeVnode(oldEndVnode, newEndVnode)){
            //
            patch(oldEndVnode, newEndVnode);
            oldEndVnode = oldChildren[--oldEndIndex]
            newEndVnode = newChildren[--newEndIndex]
        }//3 交叉比对 从头
        else if(isSomeVnode(oldStartVnode,newEndVnode)){
            patch(oldStartVnode, newEndVnode);
            oldStartVnode =oldChildren[++oldStartIndex]
            newEndVnode = newChildren[--newEndIndex];
        }//4 交叉比对 从尾
        else if(isSomeVnode(oldEndVnode,newStartVnode)){
            patch(oldEndVnode, newStartVnode);
            oldEndVnode =oldChildren[--oldStartIndex]
            newStartVnode = newChildren[++newStartIndex];
        }
    }
    //判断完毕,添加多余的子儿子  a b c  新的 a b c d
    console.log(newEndIndex)
    if (newStartIndex <= newEndIndex) {
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            
            parent.appendChild(createELm(newChildren[i]))
        }
    }
}
function isSomeVnode(oldContext, newContext) {
    // return true
    return (oldContext.tag == newContext.tag) && (oldContext.key === newContext.key);
}  



//添加属性
function updataRpors(vnode, oldProps = {}) { //第一次
    let newProps = vnode.data || {} //获取当前新节点 的属性
    let el = vnode.el //获取当前真实节点 {}
    //1老的有属性,新没有属性
    for (let key in oldProps) {
        if (!newProps[key]) {
            //删除属性
            el.removeAttribute[key] //
        }
    }
    //2演示 老的 style={color:red}  新的 style="{background:red}"
    let newStyle = newProps.style || {} //获取新的样式
    let oldStyle = oldProps.style || {} //老的
    for (let key in oldStyle) {
        if (!newStyle[key]) {
            el.style = ''
        }
    }
    //新的
    for (let key in newProps) {
        if (key === "style") {
            for (let styleName in newProps.style) {
                el.style[styleName] = newProps.style[styleName]
            }
        } else if (key === 'class') {
            el.className = newProps.class
        } else {
            el.setAttribute(key, newProps[key])
        }
    }
}
//vnode 变成真实的Dom
export function createELm(vnode) {
    let {
        tag,
        children,
        key,
        data,
        text
    } = vnode
    //注意
    if (typeof tag === 'string') { //创建元素 放到 vnode.el上
        vnode.el = document.createElement(tag) //创建元素 
        updataRpors(vnode)
        //有儿子
        children.forEach(child => {
            // 递归 儿子 将儿子渲染后的结果放到 父亲中
            vnode.el.appendChild(createELm(child))
        })
    } else { //文本
        vnode.el = document.createTextNode(text)
    }
    return vnode.el //新的dom
}

 

标签:el,Vue,oldChildren,Vnode,oldVnode,let,newChildren,diff,源码
From: https://www.cnblogs.com/FatTiger4399/p/17814677.html

相关文章

  • AQS源码分析-Condition
    在生产者消费者模型这篇文章中我们使用了ReentrantLock结合Condition实现生产者消费者模型,但我们对于ReentrantLock和Condition的工作原理并不了解,其内部的结构和源码级别实现就更加不了解了。比如在使用await方法的时候,为什么一定要用while判断条件,用if为什么不行呢?使用Condition......
  • ReentrantLock源码笔记 - 获取锁(JDK 1.8)
    ReentrantLock学习-获取锁(JDK1.8)ReentrantLock提供非公平锁与公平锁两种加锁方式,默认加锁方式为非公平锁。ReentrantLock类的结构为:从图中可以看出,ReentrantLock类包含三个静态内部类:SyncNonfairSyncFairSync其中Sync类继承AbstractQueuedSynchronize(AQS),Nonf......
  • vue:视情况绑定对应的校验。
    需求:表格内输入参数的默认值,有的参数必须,有的参数可为空,通过某个属性控制。 写两个校验规则,一个是必须有值,一个是可以为空。 首先将要校验的字段绑定在form-item的prop上。随后通过判断控制属性去绑定对应的校验规则。 ......
  • ReentrantLock源码笔记 - 释放锁(JDK 1.8)
    ReentrantLock源码学习-释放锁(unlock)上次谈到了利用ReentrantLock的非公平和公平加锁方式,那么接下来看看释放锁的流程首先调用ReentrantLock的unlock方法publicvoidunlock(){sync.release(1);}然后会调用AbstractQueuedSynchronizer(AQS)的release方法,在这个方法......
  • 直播带货源码,android editText设置颜文字过滤
    直播带货源码,androideditText设置颜文字过滤 //给editText设置过滤器  InputFilterinputFilter=newInputFilter(){    //限制输入表情    Patternemoji=Pattern.compile("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27f......
  • 在线直播源码,js 文件上传 图片上传 传输速度计算
    在线直播源码,js文件上传图片上传传输速度计算<!doctypehtml><html><head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scal......
  • 在线直播系统源码,输入框限制字节数 区分中英文
    在线直播系统源码,输入框限制字节数区分中英文   // 规则名称验证规则  不包含特殊字符   letcheckGroupName=(rule,value,callback)=>{    letreg=/^[0-9A-Za-z\u4e00-\u9fa5\(\)\(\)]+$/;     if(value!==''&&!reg.test(value)){......
  • vue:通过数组循环创建表格,表格中有输入框需校验,最后需要一次性校验所有表格。
    表格内有form表单,form表单绑定的model数据类型必须为对象。所以需要先处理一下接口请求回来的数据。 表单需要校验,校验要用到ref,所以通过索引给每个表单生成自己专属的ref。 统一写一个校验规则,绑定至form表单中的rules中,随后在表格内的输入框form-item中绑定对应的规定。......
  • zookeeper源码(04)leader选举流程
    在"zookeeper源码(03)集群启动流程"中介绍了leader选举的入口,本文将详细分析leader选举组件和流程。leader选举流程(重要)quorumPeer的start阶段使用startLeaderElection()方法启动选举LOOKING状态,投自己一票createElectionAlgorithm-创建选举核心组件:QuorumCnxManager(管......
  • vue 大文件分片上传(断点续传、并发上传、秒传)
    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。本文是基于springboot+vue实现的文件上传,本文主要介绍vue实现文件上传的步骤......