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


    好家伙,

     

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

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

     

    1.要做什么?

    本章将解决,

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

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

    实现效果如下:

     

    复制代码
        let vm1 = new Vue({data:{name:'张三'}})
        let render1 = compileToFunction(`
    • "background:yellow" key="c">我是黄色
    `) let vnode1 = render1.call(vm1) document.body.appendChild(createELm(vnode1)) //数据更新 let vm2 = new Vue({data:{name:'李四'}}) let render2 = compileToFunction(`
    • "background:blue" key="c">我是蓝色
    `) let vnode2 = render2.call(vm2) //patch 比对 setTimeout(()=>{ patch(vnode1,vnode2) },2000)
    复制代码

     

    2.思路

    复制代码
    let vm1 = new Vue({
      data: {
        name: '张三'
      }
    })
    let render1 = compileToFunction(`
    • "background:red" key="a">a
    • "background:pink" key="b">b
    • "background:blue" key="c">c
    `) let vnode1 = render1.call(vm1) document.body.appendChild(createELm(vnode1)) //数据更新 let vm2 = new Vue({ data: { name: '李四' } }) let render2 = compileToFunction(`
    • "background:red" key="a">a
    • "background:pink" key="b">b
    • "background:blue" key="c">c
    • "background:yellow" key="d">d
    `) 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  属性 
    1
    2
    tag:undefined
    if (!oldVnode.tag) { if (oldVnode.text !== Vnode.text) { return oldVnode.el.textContent = Vnode.text } } //2.1属性 (标签一样)
    1
    2
    //在updataRpors方法中处理 //方法 1直接复制 let el = Vnode.el = oldVnode.el updataRpors(Vnode, oldVnode.data) //diff子元素
    1
    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算法 做了很多优化 例子
    11
    更新为
    22
    //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 }
    复制代码

     

  • 相关阅读:
    基于Python+Django的热门旅游景点数据分析系统的设计与实现(源码+lw+部署文档+讲解等)
    Electron自动化测试技术选型调研
    Geogebra 教程之 03 没有铅笔的数学
    [附源码]Java计算机毕业设计SSM高等数学在线学习平台
    WDC西部数据闪存业务救赎之路,会成功吗?
    kubelet节点压力驱逐
    如何将驱动编译为kernel 模块
    “数聚瑞安·创新未来”中国·瑞安第四届创新创业大赛圆满举办!
    lua 时间差功能概略
    【论文精读】Evaluating Large Language Models Trained on Code
  • 原文地址:https://www.cnblogs.com/FatTiger4399/p/17814677.html