之前一直看diff算法的 新前/旧前什么的感觉一脸懵,不知道那玩意是干啥的。今天整理了下。来说说我的理解,如果说的不对,欢迎大佬们指点~
话不多说直接上手。 本文讲解虚拟dom以 snabbdom 为例子讲解
snabbdom地址
新前/新后/旧前/旧后 本质上来说就是两个虚拟dom上的开始和结束节点。那么对比自然也是四个节点之间的对比了。
在讲解对比规则之前,我们先来大致了解下patchVnode方法,也就是对比两个虚拟dom,并且更新真实dom。(讲解的 patchVnode会跳过钩子函数。)
注意:isDef 判断的是不等于 undefined。 isUndef 判断的是等于 undefined
patchVnode源码地址
对比步骤大致可以分
if (oldVnode === vnode) return
if (isDef(oldCh) && isDef(ch)) { // updateChildren 就是diff对比两个节点内容 if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) }
// 如果新的节点有children,因为上面判断过 如果 旧的children和新的children都有的情况,所以这里只需要判断一个是否存在,另一个则一定不存在 else if (isDef(ch)) { if (isDef(oldVnode.text)) api.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) }
else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) }
else if (isDef(oldVnode.text)) { api.setTextContent(elm, '') }
对应的源码八个变量
let oldStartIdx = 0; let newStartIdx = 0; let oldEndIdx = oldCh.length - 1; let oldStartVnode = oldCh[0]; let oldEndVnode = oldCh[oldEndIdx]; let newEndIdx = newCh.length - 1; let newStartVnode = newCh[0]; let newEndVnode = newCh[newEndIdx];
旧前与新前对比(也就是两个虚拟dom的开始节点对比)
if (sameVnode(oldStartVnode, newStartVnode)) { // 更新dom patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); // 指针下移 oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; }
旧后与新后对比 (两个结束节点的对比)
else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); // 指针上移 oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; }
旧后与新前对比 (旧的未处理的最后一个节点与新的未处理的第一个节点的对比)
else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); api.insertBefore( parentElm, oldStartVnode.elm!, api.nextSibling(oldEndVnode.elm!) ); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; }
旧后与新前对比完成之后如果符合条件,那么我们除了需要进行patchVnode对比更新dom之外,还需要把旧后的真实dom节点插入到旧前的前面一个节点,最后再让旧后的指针上移,新前的指针下移
如果旧后与新前匹配上了,则意味着是旧后移动了,移动到了新前的这个位置。而新前的前一个节点与旧前的前一个节点必定是相同的节点。所以这个时候我们把旧后移动到旧前的前一个节点,这样移动完的节点位置就与新前在同一个位置了。
节点移动之后
旧前与新后对比(旧的未处理的第一个节点与新的未处理的最后一个节点的对比)
else if (sameVnode(oldEndVnode, newStartVnode)) { // 更新dom patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); // 将旧前移动到旧后的后面 api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; }
旧前与新后对比完成之后如果符合条件,那么我们除了需要进行patchVnode对比更新dom之外,还需要把旧前的真实dom节点插入到旧后的后面一个节点,最后再让旧前的指针下移,新后的指针上移
缓存之后。就去这个缓存的数组中找新前对应的key所对应的索引。如果找到了:
如果说找不到:
if (isUndef(idxInOld)) { // New element api.insertBefore( parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm! ); }
if (oldStartIdx <= oldEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); }
if (newStartIdx <= newEndIdx) { // 这个before就是新后的后一个节点。如果是null,则会插入到最后一个 before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; addVnodes( parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue ); }