Diff算法(二),手写diff算法简单部分

360 阅读4分钟

这是我参与更文挑战的第 13 天,活动详情查看: 更文挑战

2021-06-13 Diff 前端造飞机

Diff算法(二),手写diff算法简单部分

1. 基本认识

首先要明白的是,前端框架里面的diff比较是在虚拟dom上的比较,因此第一节重点讲了VNode~,接下来就具体说说diff的一些基本认识

1.1 diff基本规则

  1. 在vue中diff算法是最小量更新,因此key很重要,key是这个节点的唯一标识符,告诉diff算法,在更改前后他们是同一个DOM节点
  2. 只有是同样一个虚拟节点才会做精细化比较,否则就会暴力拆除旧的,插入新的;确认是否是同一个虚拟节点的基本方针是他们的选择器相同,且key相同
  3. 只进行同层比较,不会进行跨层比较。即使是同一片的虚拟节点,只要是跨层了,就不进行精细化比较,而是直接暴力拆除旧的,然后插入新的。

1.2 diff如何处理节点

如图:

0-2-1.png

2. 第一次上树处理

2.1 创建pacth函数

  • 先要写一个patch函数 首先明确一个patch函数就是做diff算法的函数,作用就是新旧虚拟节点对比,然后虚拟节点变为真实的DOM节点,挂载到DOM树上去;

一般有3个策略,本期只讲第一种虚拟dom第一次上树和新旧对比不是同一节点暴力删除的情况

注意:h函数和VNode函数是采用上一期Diff算法(一),虚拟DOM所写的函数这里不在罗列赘述

function patch(oldVNode,newVNode){
    // 1. 判断传入的第一个参数,是dom节点还是虚拟节点
    if(oldVNode.sel === '' || oldVNode.sel === undefined ){
        // 说明是dom节点,要包装成虚拟节点
        oldVNode = VNode( oldVNode.tagName.toLowerCase(),{},[],undefined,oldVNode )
    }
    console.log( oldVNode )
    // 2. 判断oldVNode和newVNode是不是同一个节点
    if(oldVNode.key === newVNode.key && oldVNode.sel === newVNode.sel){
        // 同一个节点,精细化比较,内容较多下一期~!
        return
    }
    // 3. 是不同的节点,暴力插入新的,拆除旧的
    /**
    * @function createEle 这是一个将虚拟节点变真实节点的函数
    **/
    const newDOM = createEle(newVNode);
    oldVNode.elm.parentNode.insertBefore( newDOM ,oldVNode.elm);
    // 删除旧节点
    oldVNode.elm.parentNode.removeChild(oldVNode.elm)
}

2.2 虚拟节点变为真实节点——createEle

在上面的patch函数里面我们可以很清楚的看到有一个createEle函数将虚拟节点变真实节点的函数,那这个函数是怎么样的呢?其实很简单,其实就是js原生应用的能力,代码如下:

// 创建真实节点,作用将VNode创建为DOM
function createEle(VNode){
    const domNode = document.createElement(VNode.sel);
    if(VNode.text!=='' && ( VNode.children === undefined || VNode.children.length === 0)){
        domNode.innerText = VNode.text;
    }else if( Array.isArray(VNode.children) && VNode.children.length >0){
        VNode.children.forEach( item =>{
            const itemDOM = createEle(item)
            domNode.appendChild(itemDOM);
        })
    }
    // 因为创建生成为真实节点代表即将要上树的,前面说过,上树的时候虚拟节点的elm属性就是这个真实dom
    VNode.elm = domNode;
    return VNode.elm
}

3. VNode上树精细化比较

首先看图,看完图之后,相信大家就有一了一个清晰的了解:

0-2-2.png

看完图之后,就对前面patch函数注释的精细化部分的处理是由如下几部分组成

  1. 比较新旧节点,是同一个对象不做处理,如果新节点没有children,只有text,则直接将旧的节点文本替换成新节点的文本,旧节点的子元素全部删除
  2. 比较新旧节点,若新节点有children,旧节点没有children,那么直接将新节点的children插入到就节点的children里面即可

我们先处理比较简单的第1,2点,给patch函数注释的精细化部分添加refineCompare(oldVNode,newVNode)这个函数

// 同一个节点,精细化比较的各个处理函数

function refineCompare( oldVNode, newVNode){
    if(oldVNode === newVNode){
        return
    }
    if( newVNode.text !== undefined && ( newVNode.children === undefined || newVNode.children.length === 0)){
        console.log('命中newVNode有text属性')
        if(oldVNode.text !== newVNode.text){
            oldVNode.elm.innerHtml = '';
            oldVNode.elm.innerText = newVNode.text;
        }
    }else{
        console.log('命中newVNode没有text属性')
        // 判断老的有没有child
        if(oldVNode.children && oldVNode.children.length >0 ){
            // 老的有children,最复杂的情况,将面临最小量更新,难点,单独抽一节
            console.log('最小量更新算法')
        }else{
            // 老的没有children,直接文本替换成newVNode的children即可
            oldVNode.elm.innerText = ''
            newVNode.children.forEach(item=>{
                const dom = createEle(item);
                oldVNode.elm.appendChild(dom);
            })
        }
    }
}
  1. 比较新旧节点,若新节点有children,旧节点也有chldren,那么就将进入最难的一部分,即最小量更新

到这里,我们基本上就对vue当中的虚拟节点如何变成真实节点并挂载到DOM树上的过程就有了一个大致的了解,其实只要看到这里并理解了VNode,h函数patch函数的作用,在背点diff的八股文,面试基本上不成问题了,在有限的时间内,面试官是难不住你的!欢迎点赞,关注下一期,diff算法(三),完结篇——最小量更新