zoukankan      html  css  js  c++  java
  • path--diff

    vdom--patch(一)我们讲了,整个Vue对象初始化并渲染到页面中的过程。

    本篇文章我们主要来谈谈当页面绑定的数据修改后,是如何更新dom结构的

    vdomdiff算法,网上讲解这部分内容的文章有很多,可以互相借鉴补充。

    VueReact在更新dom时,使用的算法相同,都是基于snabbdom

    我们前面提到过,当页面绑定的数据修改时,会触发监听该数据的watcher对象更新,

    而从src/core/lifecycle.js中的mountComponent内,我们看到watcher对象更新时会调用updateComponent

    进而调用vm._update方法,vm._render()方法会依据新的数据生成新的VNode对象

    Vue.prototype._update我们会将旧新两个VNode对象传入到__patch__方法中,

    所以,最终还是回到了__patch__方法。

    流程浅析

    return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
        ...
    
        let isInitialPatch = false
        const insertedVnodeQueue = []
    
    if (isUndef(oldVnode)) { ... } else { // oldValue不是VNode,而是真实的dom元素 const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) } else { if (isRealElement) { ... } // replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) if (isDef(vnode.parent)) { // component root element replaced. // update parent placeholder node element, recursively let ancestor = vnode.parent while (ancestor) { ancestor.elm = vnode.elm ancestor = ancestor.parent } if (isPatchable(vnode)) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, vnode.parent) } } } if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }

    这次我们只传入了两个参数,分别是之前的oldVnode和数据更新后生成的新的vnode

    且它们两个都是VNode的实例。所以以上代码中如果sameVnode(oldVnode, vnode)返回true

    则执行patchVnode方法,否则和第一次渲染元素时一样,走下面的流程。

    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)
      )
    }
    
    // Some browsers do not support dynamically changing type for <input>
    // so they need to be treated as different nodes
    function sameInputType (a, b) { if (a.tag !== 'input') return true let i const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type return typeA === typeB }

    sameVnode方法是判断两个vnode可不可以复用为一个节点

    我们前面提到过,在原生创建一个dom对象时,会创建许多属性,频繁的添加删除dom元素,会造成性能的浪费。

    所以更好的做法是尽可能的复用原有的dom

    Vue中,判断两个元素可不可以复用的方法就是上面的sameVnode方法,

    即若两个vnodekey相同,tag名相同,都是或都不是注释,都有或没有data

    如果是input则要dataattrs都有或都没有,且type必须相同。

    因为我们的自定义的Vue组件,一般情况下都必须包裹在一个根元素中,

    这时sameVnode比较两个vnode对象返回的是true

    所以会直接走patchVnode方法,patchVnode方法的精髓,也就是我们常说的diff

    唯一的一种情况,就是如果组件最外层包裹的元素上有v-if语句,

    则可以通过v-else添加多个跟元素。其实这种情况,在真实渲染时,也只有一个根元素。

    但是这种情况就有可能出现可以v-ifv-else的指令完全不同,这时就会走到下面的流程。

    patchVnode

    走到patchVnode方法的前提,就是我们上面所说的oldVnodevnodesameVnode

    该方法的功能其实就是复用dom元素。我们来一步步看看它里面的逻辑处理。

    1、如果oldVnodevnode是同一个对象,则直接返回。

      if (oldVnode === vnode) {
        return
      }

    2、如果下面的表达式为真,则直接用的oldVnodeelmcomponentInstance替换vnode上的相同属性。

      if (isTrue(vnode.isStatic) &&
          isTrue(oldVnode.isStatic) &&
          vnode.key === oldVnode.key &&
          (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
        vnode.elm = oldVnode.elm
        vnode.componentInstance = oldVnode.componentInstance
        return
      }

    该条件判断主要分三部分:

    isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic)

     isStatic属性为true的条件是当前节点是静态内容根节点,所以这里vnodeoldVnode都是静态内容根节点。

    vnode.key === oldVnode.key 两个对象的key值相同

    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) 我们在生成render函数字符串中,

    会有_m_o,他们分别是renderStaticmarkOnce方法(src/core/instance/render-static.js中)。

    我们的patchVnode是在数据变化后调用,render方法是不变的,只不过因为执行render函数时数据变了,

    所以生成的vnode对象和之前不同。以_m为例,再次执行_m函数,会直接从vm._staticTrees中获取tree

    并通过cloneVNode方法克隆一份出来,这种情况下vnode.isCloned值为true

    export function renderStatic (
      index: number,
      isInFor?: boolean
    ): VNode | Array<VNode> {
      let tree = this._staticTrees[index]
      if (tree && !isInFor) {
        return Array.isArray(tree)
          ? cloneVNodes(tree)
          : cloneVNode(tree)
      }
      // otherwise, render a fresh tree.
      tree = this._staticTrees[index] =
        this.$options.staticRenderFns[index].call(this._renderProxy)
      markStatic(tree, `__static__${index}`, false)
      return tree
    }

    所以这一步主要是处理静态根节点的diff操作。

    3、以上是两种特殊情况的简单处理,通常我们会有各种各样的结点,它们就会走下面处理流程:

      let i
      const data = vnode.data
      // 调用prepatch钩子函数
      if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
        i(oldVnode, vnode)
      }
      // vnode对应的dom指向oldVnode的dom
      const elm = vnode.elm = oldVnode.elm
      // 分别获取oldVnode和vnode的子元素
      const oldCh = oldVnode.children
      const ch = vnode.children
      if (isDef(data) && isPatchable(vnode)) {
        // 更新元素上相关各种属性
        for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
        // 调用update钩子函数
        if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
      }

    处理完属性相关内容,就是对子元素内容的处理了:

      if (isUndef(vnode.text)) {
        if (isDef(oldCh) && isDef(ch)) {
          if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
        } else if (isDef(ch)) {
          if (isDef(oldVnode.text)) nodeOps.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)) {
          nodeOps.setTextContent(elm, '')
        }
      // 如果vnode是文本结点,且text有变化,则修改elm的文本内容
      } else if (oldVnode.text !== vnode.text) {
        nodeOps.setTextContent(elm, vnode.text)
      }

    3.1 如果vnode.text不是文本节点

    3.1.1 isDef(oldCh) && isDef(ch) 如果新旧Vnode对象都有子元素,且子元素不同时,通过updateChildren方法来更新子元素。这个方法是做子元素diff的重要方法,后面详细介绍。

    3.1.2 isDef(ch) vnode有子元素,而oldVnode没有子元素。如果oldVnode是文本结点,则置空(理论上新旧Vnode必须是sameVnode不应该出现这种情况)。这种情况,只需要把ch中的元素依次添加到elm中。

      function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
        for (; startIdx <= endIdx; ++startIdx) {
          createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm)
        }
      }

    该方法也很简单,就是依次调用createElm方法来创建子元素并添加到parentElm上。该方法我们在patch——创建dom中已经讲解。

    3.1.3 isDef(oldCh) vnode没有子元素,而oldVnode有子元素。这种情况只需要删掉oldVnode的子元素。

      function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
        for (; startIdx <= endIdx; ++startIdx) {
          const ch = vnodes[startIdx]
          if (isDef(ch)) {
            if (isDef(ch.tag)) {
              removeAndInvokeRemoveHook(ch)
              invokeDestroyHook(ch)
            } else { // Text node
              removeNode(ch.elm)
            }
          }
        }
      }

    如果是标签元素,则调用销毁相关的钩子函数;如果是文本结点,则直接删除文本。

    3.1.4 isDef(oldVnode.text) 否则如果oldVnode是文本结点,则直接内容置空。

    3.2 oldVnode.text !== vnode.text 如果新旧VNode对象都是文本结点,则直接修改文本内容。

    最后,调用postpatch钩子函数

      if (isDef(data)) {
        if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
      }

    updateChildren

    我们页面的dom是一个树状结构,上面所讲的patchVnode方法,是复用同一个dom元素,而如果新旧两个VNnode对象都有子元素,我们应该怎么去比较复用元素呢?这就是我们updateChildren方法所要做的事儿。

      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, elmToMove, refElm
    
        // removeOnly使用在transition-group中,其它情况下都是false
        const canMove = !removeOnly
    
        // 新旧数组都没有遍历结束
        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)
            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)
            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)
            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] : null
            if (isUndef(idxInOld)) { // New element
              createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
              newStartVnode = newCh[++newStartIdx]
            } else {
              elmToMove = oldCh[idxInOld]
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && !elmToMove) {
                warn(
                  'It seems there are duplicate keys that is causing an update error. ' +
                  'Make sure each v-for item has a unique key.'
                )
              }
              if (sameVnode(elmToMove, newStartVnode)) {
                patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
                oldCh[idxInOld] = undefined
                canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
                newStartVnode = newCh[++newStartIdx]
              } else {
                // same key but different element. treat as new element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
                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(parentElm, oldCh, oldStartIdx, oldEndIdx)
        }
      }

    乍一看这一块代码,可能有点儿懵。具体内容其实不复杂,我们先大体看一下整个判断流程,之后通过几个例子来详细过一下。

    oldStartIdxnewStartIdxoldEndIdxnewEndIdx都是指针,具体每一个指什么,相信大家都很明了,我们整个比较的过程,会不断的移动指针。

    oldStartVnodenewStartVnodeoldEndVnodenewEndVnode与上面的指针一一对应,是它们所指向的VNode结点。

    while循环在oldChnewCh遍历结束后停止,否则会不断的执行循环流程。整个流程分为以下几种情况:

    1、 如果oldStartVnode未定义,则oldCh数组遍历的起始指针后移一位。

      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      }

    注:见第七种情况,key值相同可能会置为undefined

    2、 如果oldEndVnode未定义,则oldCh数组遍历的起始指针前移一位。

      else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } 

    注:见第七种情况,key值相同可能会置为undefined

    3、sameVnode(oldStartVnode, newStartVnode),这里判断两个数组起始指针所指向的对象是否可以复用。如果返回真,则先调用patchVnode方法复用dom元素并递归比较子元素,然后oldChnewCh的起始指针分别后移一位。

      else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      }

    4、sameVnode(oldEndVnode, newEndVnode),这里判断两个数组结束指针所指向的对象是否可以复用。如果返回真,则先调用patchVnode方法复用dom元素并递归比较子元素,然后oldChnewCh的结束指针分别前移一位。

      else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } 

    5、sameVnode(oldStartVnode, newEndVnode),这里判断oldCh起始指针指向的对象和newCh结束指针所指向的对象是否可以复用。如果返回真,则先调用patchVnode方法复用dom元素并递归比较子元素,因为复用的元素在newCh中是结束指针所指的元素,所以把它插入到oldEndVnode.elm的前面。最后oldCh的起始指针后移一位,newCh的起始指针分别前移一位。

      else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      }

    6、sameVnode(oldEndVnode, newStartVnode),这里判断oldCh结束指针指向的对象和newCh起始指针所指向的对象是否可以复用。如果返回真,则先调用patchVnode方法复用dom元素并递归比较子元素,因为复用的元素在newCh中是起始指针所指的元素,所以把它插入到oldStartVnode.elm的前面。最后oldCh的结束指针前移一位,newCh的起始指针分别后移一位。

      else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      }

    7、如果上述六种情况都不满足,则走到这里。前面的比较都是头尾组合的比较,这里的情况,稍微更加复杂一些,其实主要就是根据key值来复用元素。

    ① 遍历oldCh数组,找出其中有key的对象,并以key为键,索引值为value,生成新的对象oldKeyToIdx

    if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    function createKeyToOldIdx (children, beginIdx, endIdx) {
      let i, key
      const map = {}
      for (i = beginIdx; i <= endIdx; ++i) {
        key = children[i].key
        if (isDef(key)) map[key] = i
      }
      return map
    }

    ② 查询newStartVnode是否有key值,并查找oldKeyToIdx是否有相同的key

      idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null

    ③ 如果newStartVnode没有keyoldKeyToIdx没有相同的key,则调用createElm方法创建新元素,newCh的起始索引后移一位。

      if (isUndef(idxInOld)) { // New element
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
        newStartVnode = newCh[++newStartIdx]
      } 

    ④ elmToMove保存的是要移动的元素,如果sameVnode(elmToMove, newStartVnode)返回真,说明可以复用,这时先调用patchVnode方法复用dom元素并递归比较子元素,重置oldCh中相对于的元素为undefined,然后把当前元素插入到oldStartVnode.elm前面,newCh的起始索引后移一位。如果sameVnode(elmToMove, newStartVnode)返回假,例如tag名不同,则调用createElm方法创建新元素,newCh的起始索引后移一位。

      elmToMove = oldCh[idxInOld]
      if (sameVnode(elmToMove, newStartVnode)) {
        patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
        oldCh[idxInOld] = undefined
        canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
        newStartVnode = newCh[++newStartIdx]
      } else {
        // same key but different element. treat as new element
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
        newStartVnode = newCh[++newStartIdx]
      }

    上面的内容比较枯燥,我们一起通过几个例子来看一下它的处理流程。

    没有key值的情况

    假设oldCh上有五个元素abcdenewCh有六个元素debfda,且没有key值。初始情况下,页面中dom顺序为abcde

    第一次while循环,oldStartVnodenewEndVnode都是a,所以会走到第五种情况,a元素会把插入到e元素的下一个元素前。此时页面中dom变为bcdeaoldStartVnode指向bnewEndVnode指向d

    第二次while循环,因为头尾都不相同,走到最后第七种情况,然后会创建新的元素d并插入到b前面。此时页面中dom变为dbcdeanewStartVnode指向e

    第三次while循环,oldEndVnodenewStartVnode都是e,所以会走到第六种情况,e元素会把插入到b元素之前。此时页面中dom变为debcdaoldEndVnode指向dnewStartVnode指向b

    第四次while循环,oldStartVnodenewStartVnode都指向b,所以会走到第三种情况,直接复用b元素。此时页面中dom依然为debcdaoldStartVnode指向cnewStartVnode指向f

    第五次while循环,oldEndVnodenewEndVnode都指向d,所以会走到第四种情况,直接复用d元素。此时页面中dom依然为debcdaoldEndVnode指向cnewEndVnode指向f

    第六次while循环,两个数组中都只剩下一个没有遍历的元素且不相同,所以会走到第七种情况。然后会创建新的元素f并插入到c前面。此时页面中dom变为debfcdanewStartVnode指向b

    这时newStartIdx会大于newEndIdx,所以会终止循环。这时我们发现,页面中多了c元素。所以updateChildren方法在循环之后还有删除无用的旧结点的操作。

    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(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }

    第一种情况是oldCh中的元素全部复用。则依次把newStartIdxnewEndIdx之间的元素插入到相应的位置。

    第二种情况是newCh中的元素全部复用。则依次删除oldStartIdxoldEndIdx之间的元素,我们上面例子中的c元素在这里就会被删除。

    key值的情况

    假设oldCh上有五个元素adiv[key=1]footer[key=3]span[key=2]pnewCh有六个元素p[key=3]span[key=2]pdiv[key=1]aspan。初始情况下,页面中dom顺序为a、div[key=1]、footer[key=3]、span[key=2]、p

    第一次while循环,头尾都不可复用,所以会走到第七种情况,此时会生成oldKeyToIdx,如下:

    oldKeyToIdx = {
      '1': 1,
      '2': 3,
      '3': 2
    }

    newStartVnode元素p[key=3]根据key值比较,elmToMove会指向footer[key=3],但因为它们标签名不一样,所以sameVNode判断会返回false。所以直接插入到a前面,页面中dom变为p[key=3]、a、div[key=1]、footer[key=3]、span[key=2]、pnewStartVnode指向span[key=2]

    第二次while循环,同样头尾都不可复用,所以会走到第七种情况,newStartVnode元素span[key=2]根据key值比较,elmToMove会指向span[k=2],两元素可以复用,span[k=2]会被插入到a前面,页面中dom变为p[key=1]、span[key=2]、a、div[key=1]、footer[k=3]、pnewStartVnode指向div。同时oldCh变为[adiv[key=1]footer[k=3], undefined, p]。

    第三次while循环,oldEndVnodenewStartVnode都是p,所以走到第六种情况,dom中最后的p元素会插入到a元素,页面中dom顺序变为p[key=1]、span[key=2]、p、a、div[key=1]、footer[k=3]oldEndVnode前移一位指向了undefinednewStartVnode后移一位指向div[key=1]

    第四次while循环,oldEndVnode返回undefined所以会走到第二种情况。页面中dom不变,oldEndVnode指向footer[k=3]

    第五次while循环,依然头尾都不可复用,走到第七种情况,newStartVnode根据key=1找到可以复用的div[key=1],该元素会插入到a元素之前,页面中的dom变为p[key=1]、span[key=2]、p、div[key=1]、a、footer[k=3],同时oldCh变为[a, undefined, footer[k=3], undefined, p],newStartVnode后移一位指向a`。

    第六次while循环,newStartVnodeoldStartVnode都指向a,可以直接复用走到第三种情况。页面中dom不变,newStartVnode后移一位指向spanoldStartVnode后移一位指向undefined

    第七次while循环,oldStartVnode返回undefined所以会走到第一种情况。页面中dom不变,oldStartVnode指向footer[k=3]

    第八次while循环,新旧没有比较的子元素都只剩一个,且不可复用,会走到第七种情况,页面中会创建span,并插入到footer[k=3]之前。此时页面中dom变为p[key=1]、span[key=2]、p、div[key=1]、a、span、footer[k=3]newStartVnode指向超出newCh范围,指向undefined

    这时newStartIdx会大于newEndIdx,所以会终止循环。与之前例子一样,最终会删除多余的footer[k=3]

    以上基本就是vdomdiff相关主要内容。

  • 相关阅读:
    Linux下利用rsync实现多服务器文件同步
    SVN使用import导入新数据到版本库
    SVN协同开发时服务端与线上APACHE测试环境网站同步记录 转
    [转]rsync的配置与应用
    Matrix 旋转mc 注册点在mc的左上角
    多边形面积计算
    【神奇的代码】
    【碰撞回弹】
    三角函数
    判断点是否在线段或直线上
  • 原文地址:https://www.cnblogs.com/dhsz/p/7685323.html
Copyright © 2011-2022 走看看