# 虚拟DOM之更新子节点(一)

# 回顾

在对比子节点差异的时候,我们需要区分新旧子节点的不同情况:

  • 旧:单个子节点 && 新:单个子节点
  • 旧:单个子节点 && 新:没有子节点
  • 旧:单个子节点 && 新:多个子节点
  • 旧:没有子节点 && 新:单个子节点
  • 旧:没有子节点 && 新:没有子节点
  • 旧:没有子节点 && 新:多个子节点
  • 旧:多个子节点 && 新:单个子节点
  • 旧:多个子节点 && 新:没有子节点

这8中情况,都非常简单,就不过多论述了。

再先看一下,我们之前是怎么操作新旧节点均为多个子节点的情况的:

遍历旧的子节点,将其全部移除:

for (let i = 0; i < prevChildren.length; i++) {
    removeChild(container,prevChildren[i].el)
}

遍历新的子节点,将其全部挂载

for (let i = 0; i < nextChildren.length; i++) {
    mount(nextChildren[i], container)
}

在浏览器中操作DOM是极为昂贵的,这种操作方式需要不停的对DOM进行删除和创建的操作,但是对于新旧节点差异很小的情况下,无法尽可能的复用原有DOM节点,从而造成性能上的浪费。

看一个简单的例子:

按照之前的逻辑,原有子节点删除,重新创建所有的新子节点,无疑会造成性能上的巨大浪费。

我们真正想做的,其实是找出二者之间的区别,针对二者的不同进行修改。

找出新旧子节点的不同,并针对不同之处进行更新的过程,其实就是我们常说的核心Diff算法。

# React/Vue中的key

Vue对于Key的说明:

key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。

React对于Key的说明:

当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。

总结

新子节点根据自己的key找到对应的旧子节点。

通过key来查找新旧子节点的对应关系其实很好理解,但不论是React还是Vue,都支持不传递Key的使用方式,那么这种方式,新旧子节点如何查找对应关系的呢?

# 无Key的Diff

在没有key的时候,为了能够复用原有的dom,所以我们需要通过新旧子节点的顺序来进行比对。

当新旧子节点长度相同时:

当新子节点长度大于旧子节点长度:

当新子节点长度小于旧子节点长度:

简单代码:

const noKeyDiff = function(prevChildren,nextChildren,container){
    //旧长度
    let prevLen = prevChildren.length;
    //新长度
    let nextLen = nextChildren.length;
    //最小长度
    let commonLen = Math.min(prevLen,nextLen);
    //长度相同
    for(let i = 0;i<commonLen;i++){
        patch(prevChildren[i],nextChildren[i],container)
    }
    //新长大于旧长,挂载
    if(nextLen > prevLen){
        for(let i = commonLen; i<nextLen; i++){
            mount(nextChildren[i],container);
        }
    }
    //新长小于旧长,删除
    else if(prevLen > nextLen){
        for(let i = commonLen; i<prevLen; i++){
            removeChild(container,prevChildren[i].el)
        }
    }
}

# 无key的好处

对于简单DOM的情况,性能更好~

因为至于一次循环,就处理了所有的新旧子节点,当key存在的时候,无论使用何种diff优化算法,都不可避免的需要进行多重循环来进行key的比对。