能打开的a站,广东网站设计哪家好,哪个公司的室内设计公司,厦门小程序开发的公司1. 前言
在上一篇文章中#xff0c;我们了解了Vue中的patch过程#xff0c;即DOM-Diff算法。并且知道了在patch过程中基本会干三件事#xff0c;分别是#xff1a;创建节点#xff0c;删除节点和更新节点。创建节点和删除节点都比较简单#xff0c;而更新节点因为要处理…1. 前言
在上一篇文章中我们了解了Vue中的patch过程即DOM-Diff算法。并且知道了在patch过程中基本会干三件事分别是创建节点删除节点和更新节点。创建节点和删除节点都比较简单而更新节点因为要处理各种可能出现的情况所以逻辑略微复杂一些但是没关系我们通过分析过程对照源码画逻辑流程图来帮助我们理解了其中的过程。最后我们还遗留了一个问题那就是在更新节点过程中新旧VNode可能都包含有子节点对于子节点的对比更新会有额外的一些逻辑那么在本篇文章中我们就来学习在Vue中是怎么对比更新子节点的。
2. 更新子节点
当新的VNode与旧的oldVNode都是元素节点并且都包含子节点时那么这两个节点的VNode实例上的children属性就是所包含的子节点数组。我们把新的VNode上的子节点数组记为newChildren把旧的oldVNode上的子节点数组记为oldChildren我们把newChildren里面的元素与oldChildren里的元素一一进行对比对比两个子节点数组肯定是要通过循环外层循环newChildren数组内层循环oldChildren数组每循环外层newChildren数组里的一个子节点就去内层oldChildren数组里找看有没有与之相同的子节点伪代码如下
for (let i 0; i newChildren.length; i) {const newChild newChildren[i];for (let j 0; j oldChildren.length; j) {const oldChild oldChildren[j];if (newChild oldChild) {// ...}}
}那么以上这个过程将会存在以下四种情况 创建子节点 如果newChildren里面的某个子节点在oldChildren里找不到与之相同的子节点那么说明newChildren里面的这个子节点是之前没有的是需要此次新增的节点那么就创建子节点。 删除子节点 如果把newChildren里面的每一个子节点都循环完毕后发现在oldChildren还有未处理的子节点那就说明这些未处理的子节点是需要被废弃的那么就将这些节点删除。 移动子节点 如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点但是所处的位置不同这说明此次变化需要调整该子节点的位置那就以newChildren里子节点的位置为基准调整oldChildren里该节点的位置使之与在newChildren里的位置相同。 更新节点 如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点并且所处的位置也相同那么就更新oldChildren里该节点使之与newChildren里的该节点相同。
OK到这里逻辑就相对清晰了接下来我们只需分门别类的处理这四种情况就好了。
3. 创建子节点
如果newChildren里面的某个子节点在oldChildren里找不到与之相同的子节点那么说明newChildren里面的这个子节点是之前没有的是需要此次新增的节点那么我们就创建这个节点创建好之后再把它插入到DOM中合适的位置。
创建节点这个很容易我们在上一篇文章的第三章已经介绍过了这里就不再赘述了。
那么创建好之后如何插入到DOM中的合适的位置呢显然把节点插入到DOM中是很容易的找到合适的位置是关键。接下来我们分析一下如何找这个合适的位置。我们看下面这个图 上图中左边是新的VNode右边是旧的oldVNode同时也是真实的DOM。这个图意思是当我们循环newChildren数组里面的子节点前两个子节点都在oldChildren里找到了与之对应的子节点那么我们将其处理处理过后把它们标志为已处理当循环到newChildren数组里第三个子节点时发现在oldChildren里找不到与之对应的子节点那么我们就需要创建这个节点创建好之后我们发现这个节点本是newChildren数组里左起第三个子节点那么我们就把创建好的节点插入到真实DOM里的第三个节点位置也就是所有已处理节点之后OK此时我们拍手称快所有已处理节点之后就是我们要找的合适的位置但是真的是这样吗我们再来看下面这个图 假如我们按照上面的方法把第三个节点插入到所有已处理节点之后此时如果第四个节点也在oldChildren里找不到与之对应的节点也是需要创建的节点那么当我们把第四个节点也按照上面的说的插入到已处理节点之后发现怎么插入到第三个位置了可明明这个节点在newChildren数组里是第四个啊
这就是问题所在其实我们应该把新创建的节点插入到所有未处理节点之前这样以来逻辑才正确。后面不管有多少个新增的节点每一个都插入到所有未处理节点之前位置才不会错。
所以合适的位置是所有未处理节点之前而并非所有已处理节点之后。
4. 删除子节点
如果把newChildren里面的每一个子节点都循环一遍能在oldChildren数组里找到的就处理它找不到的就新增直到把newChildren里面所有子节点都过一遍后发现在oldChildren还存在未处理的子节点那就说明这些未处理的子节点是需要被废弃的那么就将这些节点删除。
删除节点这个也很容易我们在上一篇文章的第四章已经介绍过了这里就不再赘述了。
5. 更新子节点
如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点并且所处的位置也相同那么就更新oldChildren里该节点使之与newChildren里的该节点相同。
关于更新节点我们在上一篇文章的第五章已经介绍过了这里就不再赘述了。
6. 移动子节点
如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点但是所处的位置不同这说明此次变化需要调整该子节点的位置那就以newChildren里子节点的位置为基准调整oldChildren里该节点的位置使之与在newChildren里的位置相同。
同样移动一个节点不难关键在于该移动到哪或者说关键在于移动到哪个位置这个位置才是关键。我们看下图 在上图中绿色的两个节点是相同节点但是所处位置不同即newChildren里面的第三个子节点与真实DOM即oldChildren里面的第四个子节点相同但是所处位置不同按照上面所说的我们应该以newChildren里子节点的位置为基准调整oldChildren里该节点的位置所以我们应该把真实DOM即oldChildren里面的第四个节点移动到第三个节点的位置通过上图中的标注我们不难发现所有未处理节点之前就是我们要移动的目的位置。如果此时你说那可不可以移动到所有已处理节点之后呢那就又回到了更新节点时所遇到的那个问题了如果前面有新增的节点呢
7. 回到源码
OK以上就是更新子节点时所要考虑的所有情况了分析完以后我们回到源码里看看实际情况是不是我们分析的这样子的源码如下
// 源码位置 /src/core/vdom/patch.jsif (isUndef(idxInOld)) { // 如果在oldChildren里找不到当前循环的newChildren里的子节点// 新增节点并插入到合适位置createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {// 如果在oldChildren里找到了当前循环的newChildren里的子节点vnodeToMove oldCh[idxInOld]// 如果两个节点相同if (sameVnode(vnodeToMove, newStartVnode)) {// 调用patchVnode更新节点patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)oldCh[idxInOld] undefined// canmove表示是否需要移动节点如果为true表示需要移动则移动节点如果为false则不用移动canMove nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)}
}以上代码中首先判断在oldChildren里能否找到当前循环的newChildren里的子节点如果找不到那就是新增节点并插入到合适位置如果找到了先对比两个节点是否相同若相同则先调用patchVnode更新节点更新完之后再看是否需要移动节点注意源码里在判断是否需要移动子节点时用了简写的方式下面这两种写法是等价的
canMove nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
// 等同于
if(canMove){nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
}我们看到源码里的实现跟我们分析的是一样一样的。
8. 总结
本篇文章我们分析了Vue在更新子节点时是外层循环newChildren数组内层循环oldChildren数组把newChildren数组里的每一个元素分别与oldChildren数组里的每一个元素匹配根据不同情况作出创建子节点、删除子节点、更新子节点以及移动子节点的操作。并且我们对不同情况的不同操作都进行了深入分析分析之后我们回到源码验证我们分析的正确性发现我们的分析跟源码的实现是一致的。
最后我们再思考一个问题这样双层循环虽然能解决问题但是如果节点数量很多这样循环算法的时间复杂度会不会很高有没有什么可以优化的办法答案当然是有的并且Vue也意识到了这点也进行了优化那么下篇文章我们就来分析当节点数量很多时Vue是怎么优化算法的。