极为细致的Vue的Diff流程详解——以流程图表达

作者:广工小成

来源:SegmentFault 思否


网上看了一些diff的算法,但是感觉看完之后,还是那么的一知半解,为什么一个简单的diff算法,不能直接画个流程图就简单的明了了呢,说动就动,下面的是本人基于vue版本2.6.11源码为各位读友进行的解析

Vue的diff流程图


流程前说明

  1. 由于diff的过程是对vnode(虚拟dom)树进行层级比较,所以以同一层级作为例子

  2. 下面将旧节点列表的起始和终止节点称为OS(OldStarVnode)和OE(OldEndVnode),用index标志遍历过程OS和OE的变化。即OS和OE的index称为OSIndex和OEIndex。同理得新节点的为NS和NE,NSIndex和NEIndex,如下图


主流程

如下图:

文字版描述一下就是:

  1. 判断是否遍历完,未遍历则开始2,否则,如果遍历完了旧节点列表,则未遍历的新节点则创建并且增加到节点列表,如果遍历完了新节点列表,则未遍历的旧节点在节点列表里面删除

  2. 对旧节点的OS和OE进行判空,如果为空,则跳过该节点,继续从1开始;否则继续3

  3. 对OS,OE,NS,NE进行两两比较,如果相等,则更新节点并且指针向下一个移动,继续从1开始;否则继续4

  4. 判断NS是否有key,有key则判断NS是否在旧节点列表里面找到key一样的进行更新;否则创建NS并且插入节点列表


updateChildren进行diff算法源码


function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {    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]    let oldKeyToIdx, idxInOld, vnodeToMove, refElm
    // removeOnly is a special flag used only by <transition-group>    // to ensure removed elements stay in correct relative positions    // during leaving transitions    const canMove = !removeOnly
    if (process.env.NODE_ENV !== 'production') {      checkDuplicateKeys(newCh)    }
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {      if (isUndef(oldStartVnode)) {        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left      } else if (isUndef(oldEndVnode)) {        oldEndVnode = oldCh[--oldEndIdx]      } else if (sameVnode(oldStartVnode, newStartVnode)) {        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)        oldStartVnode = oldCh[++oldStartIdx]        newStartVnode = newCh[++newStartIdx]      } else if (sameVnode(oldEndVnode, newEndVnode)) {        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)        oldEndVnode = oldCh[--oldEndIdx]        newEndVnode = newCh[--newEndIdx]      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))        oldStartVnode = oldCh[++oldStartIdx]        newEndVnode = newCh[--newEndIdx]      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)        oldEndVnode = oldCh[--oldEndIdx]        newStartVnode = newCh[++newStartIdx]      } else {        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)        idxInOld = isDef(newStartVnode.key)          ? oldKeyToIdx[newStartVnode.key]          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)        if (isUndef(idxInOld)) { // New element          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)        } else {          vnodeToMove = oldCh[idxInOld]          if (sameVnode(vnodeToMove, newStartVnode)) {            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)            oldCh[idxInOld] = undefined            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)          } else {            // same key but different element. treat as new element            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)          }        }        newStartVnode = newCh[++newStartIdx]      }    }    if (oldStartIdx > oldEndIdx) {      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)    } else if (newStartIdx > newEndIdx) {      removeVnodes(oldCh, oldStartIdx, oldEndIdx)    }  }




附,源码中部分工具函数的解释:


isUndef 对节点进行判空

function isUndef (v) {  return v === undefined || v === null}


sameVnode对节点进行判断是否相等

  1. 判断新旧节点的key

  2. 判断新旧节点的属性(tag,isComment表示是否是注释节点,isDef表示是否为非空节点,sameInputType表示是否同个Input节点)是否一致

  3. 判断新旧节点的加载函数asyncFactory是否一致



function sameVnode (a, b) {  return (    a.key === b.key && (      (        a.tag === b.tag &&        a.isComment === b.isComment &&        isDef(a.data) === isDef(b.data) &&        sameInputType(a, b)      ) || (        isTrue(a.isAsyncPlaceholder) &&        a.asyncFactory === b.asyncFactory &&        isUndef(b.asyncFactory.error)      )    )  )}




patchVnode更新节点


patchVnode更新节点主要做以下事情,代码比较长就不贴了,影响读者,需要可以直接阅读源码:

  1. 判断vnode和oldvnode是否相等,相等直接返回

  2. 处理静态节点的情况

  3. 对vnode如果是可patch的情形进行调用update

  4. 对vnode进行判断是否是根节点(即文本节点),如果是,则进行5,否则则对其子节点进行遍历更新

  5. 判断vnode和oldvnode文本是否一样: 不一样则替换节点文本

❤️爱心三连击

1.看到这里了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。
2.关注公众号程序员成长指北,回复「1」加入高级前端交流群!「在这里有好多 前端 开发者,会讨论 前端 Node 知识,互相学习」!
3.也可添加微信【ikoala520】,一起成长。


“在看转发”是最大的支持

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值