模板tamplate通过parseoptimizegenerate等一些列操作之后,把AST转为render function code进而生成虚构VNode,模板编译阶段根本曾经实现了,那么这一章,咱们来探讨一下Vue中的一个算法策略--dom diff 首先来介绍下什么叫dom diff

什么是虚构dom

咱们通过后面的章节学习曾经晓得,要晓得渲染实在DOM的开销是很大的,比方有时候咱们批改了某个数据,如果间接渲染到实在dom上会引起整个dom树重绘重排,有没有可能咱们只更新咱们批改的那一小块dom而不要更新整个dom呢?

为了解决这个问题,咱们的解决方案是--依据实在DOM生成一颗virtual DOM,当virtual DOM某个节点的数据扭转后会生成一个新的Vnode,而后Vnode和oldVnode作比照,发现有不一样的中央就间接批改在实在的DOM上,而后使oldVnode的值为Vnode。这也就是咱们所说的一个虚构dom diff的过程

图示

传统的Diff算法所消耗的工夫复杂度为O(n^3),那么这个O(n^3)是怎么算进去的?

  1. 传统diff算法工夫复杂度为n(第一次Old与新的所有节点比照)----O(n)
  2. 传统diff算法工夫复杂度为n(第二次Old树的所有节点与新的所有节点比照)----O(n^2)
  3. 新树的生成,节点可变编辑,工夫复杂度为n(遍历以后树)----O(n^3)

第一次比照 (1:n)

第二次比照 (1:n)

第n次比照 (n:n)

到这里那么n个节点与n个节点暴力比照就比照完了,那么就开启第三轮可编辑树节点遍历,更改之后的树由vdom(old)vdom(new)

故而传统diff算法O(n^3)是这么算进去的,然而这不是咱们明天钻研的重点。

古代diff算法

古代diff算法策略说的是,同层级比拟,广度优先

那么这里的话咱们要深刻源码了,在深刻源码之前咱们在心中应该造成这样一个概念,整个diff的流程是什么?咱们再比照着源码解读

diff算法流程图

深刻源码

咱们在Vue初始化的时候调用lifecycleMixin函数的时候,会给Vue的原型上挂载_update办法

_update

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {    const vm: Component = this    if (vm._isMounted) {      //会调用申明周期中的beforeUpdate回调函数      callHook(vm, 'beforeUpdate')    }    const prevEl = vm.$el    const prevVnode = vm._vnode    const prevActiveInstance = activeInstance    activeInstance = vm    vm._vnode = vnode    // Vue.prototype.__patch__ is injected in entry points    // based on the rendering backend used.    //若组件自身的vnode未生成,间接用传入的vnode生成dom    if (!prevVnode) {      // initial render      vm.$el = vm.__patch__(        vm.$el, vnode, hydrating, false /* removeOnly */,        vm.$options._parentElm,        vm.$options._refElm      )      // no need for the ref nodes after initial patch      // this prevents keeping a detached DOM tree in memory (#5851)      vm.$options._parentElm = vm.$options._refElm = null    } else {      //对新旧vnode进行diff      // updates      vm.$el = vm.__patch__(prevVnode, vnode)    }    activeInstance = prevActiveInstance    // update __vue__ reference    if (prevEl) {      prevEl.__vue__ = null    }    if (vm.$el) {      vm.$el.__vue__ = vm    }    // if parent is an HOC, update its $el as well    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {      vm.$parent.$el = vm.$el    }

咱们在这里能够看到vm.$el = vm.__patch__办法,追根溯源_patch_的定义:

Vue.prototype.__patch__ = inBrowser ? patch : noop

可见这里是一个浏览器环境的甄别,如果在浏览器环境中,咱们会执行patch,不在的话会执行noop,这是一个util外面的一个办法,用来跨平台的,咱们这里临时不思考,接着咱们去看patch的具体实现./patch文件,参考vue实战视频解说:进入学习

import * as nodeOps from 'web/runtime/node-ops'import { createPatchFunction } from 'core/vdom/patch'import baseModules from 'core/vdom/modules/index'import platformModules from 'web/runtime/modules/index'const modules = platformModules.concat(baseModules)export const patch: Function = createPatchFunction({ nodeOps, modules })

createPatchFunction函数

/** * 创立patch办法 */export function createPatchFunction (backend) {  let i, j  const cbs = {}  const { modules, nodeOps } = backend  for (i = 0; i < hooks.length; ++i) {    cbs[hooks[i]] = []    for (j = 0; j < modules.length; ++j) {      if (isDef(modules[j][hooks[i]])) {        cbs[hooks[i]].push(modules[j][hooks[i]])      }    }  }  function emptyNodeAt (elm) {    return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)  }  /**   * 创立一个回调办法, 用于删除节点   *    *    */  function createRmCb (childElm, listeners) {    function remove () {      if (--remove.listeners === 0) {        removeNode(childElm)      }    }    remove.listeners = listeners    return remove  }  function removeNode (el) {    const parent = nodeOps.parentNode(el)    // element may have already been removed due to v-html / v-text    if (isDef(parent)) {      nodeOps.removeChild(parent, el)    }  }  /**   * 通过vnode的tag判断是否是原生dom标签或者组件标签   * 用于创立实在DOM节点时, 预先判断tag的合法性   */  function isUnknownElement (vnode, inVPre) {    return (      !inVPre &&      !vnode.ns &&      !(        config.ignoredElements.length &&        config.ignoredElements.some(ignore => {          return isRegExp(ignore)            ? ignore.test(vnode.tag)            : ignore === vnode.tag        })      ) &&      config.isUnknownElement(vnode.tag)    )  }  let creatingElmInVPre = 0  // 创立一个节点  function createElm (    vnode,    insertedVnodeQueue,    parentElm,    refElm,    nested,    ownerArray,    index  ) {    // 节点曾经被渲染, 须要应用一个克隆节点    if (isDef(vnode.elm) && isDef(ownerArray)) {      // This vnode was used in a previous render!      // now it's used as a new node, overwriting its elm would cause      // potential patch errors down the road when it's used as an insertion      // reference node. Instead, we clone the node on-demand before creating      // associated DOM element for it.      vnode = ownerArray[index] = cloneVNode(vnode)    }    // 创立组件节点 详见本文件中的createComponent办法    vnode.isRootInsert = !nested // for transition enter check    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {      return    }    const data = vnode.data    const children = vnode.children    const tag = vnode.tag    /**     * 如果要创立的节点有tag属性, 这里做一下校验     * 如果该节点下面有v-pre指令, 间接给flag加1     * 如果没有v-pre须要调用isUnknownElement判断标签是否非法, 而后给出正告     */    if (isDef(tag)) {      if (process.env.NODE_ENV !== 'production') {        if (data && data.pre) {          creatingElmInVPre++        }        if (isUnknownElement(vnode, creatingElmInVPre)) {          warn(            'Unknown custom element: <' + tag + '> - did you ' +            'register the component correctly? For recursive components, ' +            'make sure to provide the "name" option.',            vnode.context          )        }      }      vnode.elm = vnode.ns        ? nodeOps.createElementNS(vnode.ns, tag)        : nodeOps.createElement(tag, vnode)      setScope(vnode)      /* istanbul ignore if */      if (__WEEX__) {        // in Weex, the default insertion order is parent-first.        // List items can be optimized to use children-first insertion        // with append="tree".        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)        if (!appendAsTree) {          if (isDef(data)) {            invokeCreateHooks(vnode, insertedVnodeQueue)          }          insert(parentElm, vnode.elm, refElm)        }        createChildren(vnode, children, insertedVnodeQueue)        if (appendAsTree) {          if (isDef(data)) {            invokeCreateHooks(vnode, insertedVnodeQueue)          }          insert(parentElm, vnode.elm, refElm)        }      } else {        createChildren(vnode, children, insertedVnodeQueue)        if (isDef(data)) {          invokeCreateHooks(vnode, insertedVnodeQueue)        }        insert(parentElm, vnode.elm, refElm)      }      if (process.env.NODE_ENV !== 'production' && data && data.pre) {        creatingElmInVPre--      }    } else if (isTrue(vnode.isComment)) {      vnode.elm = nodeOps.createComment(vnode.text)      insert(parentElm, vnode.elm, refElm)    } else {      vnode.elm = nodeOps.createTextNode(vnode.text)      insert(parentElm, vnode.elm, refElm)    }  }  /**   * 创立组件   * 如果组件实例曾经存在, 只须要初始化组件并从新激活组件即可   */  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {    let i = vnode.data    if (isDef(i)) {      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive      if (isDef(i = i.hook) && isDef(i = i.init)) {        i(vnode, false /* hydrating */, parentElm, refElm)      }      // after calling the init hook, if the vnode is a child component      // it should've created a child instance and mounted it. the child      // component also has set the placeholder vnode's elm.      // in that case we can just return the element and be done.      if (isDef(vnode.componentInstance)) {        initComponent(vnode, insertedVnodeQueue)        if (isTrue(isReactivated)) {          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)        }        return true      }    }  }  /**   * 初始化组件   * 次要的操作是已插入的vnode队列, 触发create钩子, 设置style的scope, 注册ref   */  function initComponent (vnode, insertedVnodeQueue) {    if (isDef(vnode.data.pendingInsert)) {      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)      vnode.data.pendingInsert = null    }    vnode.elm = vnode.componentInstance.$el    if (isPatchable(vnode)) {      invokeCreateHooks(vnode, insertedVnodeQueue)      setScope(vnode)    } else {      // empty component root.      // skip all element-related modules except for ref (#3455)      registerRef(vnode)      // make sure to invoke the insert hook      insertedVnodeQueue.push(vnode)    }  }  /**   * 激活组件   */  function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {    let i    // hack for #4339: a reactivated component with inner transition    // does not trigger because the inner node's created hooks are not called    // again. It's not ideal to involve module-specific logic in here but    // there doesn't seem to be a better way to do it.    let innerNode = vnode    while (innerNode.componentInstance) {      innerNode = innerNode.componentInstance._vnode      if (isDef(i = innerNode.data) && isDef(i = i.transition)) {        for (i = 0; i < cbs.activate.length; ++i) {          cbs.activate[i](emptyNode, innerNode)        }        insertedVnodeQueue.push(innerNode)        break      }    }    // unlike a newly created component,    // a reactivated keep-alive component doesn't insert itself    insert(parentElm, vnode.elm, refElm)  }  /**   * 插入节点, 有父节点的插入到后面, 没有的插入到前面   */  function insert (parent, elm, ref) {    if (isDef(parent)) {      if (isDef(ref)) {        if (ref.parentNode === parent) {          nodeOps.insertBefore(parent, elm, ref)        }      } else {        nodeOps.appendChild(parent, elm)      }    }  }  function createChildren (vnode, children, insertedVnodeQueue) {    if (Array.isArray(children)) {      if (process.env.NODE_ENV !== 'production') {        checkDuplicateKeys(children)      }      for (let i = 0; i < children.length; ++i) {        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)      }    } else if (isPrimitive(vnode.text)) {      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))    }  }  function isPatchable (vnode) {    while (vnode.componentInstance) {      vnode = vnode.componentInstance._vnode    }    return isDef(vnode.tag)  }  function invokeCreateHooks (vnode, insertedVnodeQueue) {    for (let i = 0; i < cbs.create.length; ++i) {      cbs.create[i](emptyNode, vnode)    }    i = vnode.data.hook // Reuse variable    if (isDef(i)) {      if (isDef(i.create)) i.create(emptyNode, vnode)      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)    }  }  // set scope id attribute for scoped CSS.  // this is implemented as a special case to avoid the overhead  // of going through the normal attribute patching process.  function setScope (vnode) {    let i    if (isDef(i = vnode.fnScopeId)) {      nodeOps.setStyleScope(vnode.elm, i)    } else {      let ancestor = vnode      while (ancestor) {        if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {          nodeOps.setStyleScope(vnode.elm, i)        }        ancestor = ancestor.parent      }    }    // for slot content they should also get the scopeId from the host instance.    if (isDef(i = activeInstance) &&      i !== vnode.context &&      i !== vnode.fnContext &&      isDef(i = i.$options._scopeId)    ) {      nodeOps.setStyleScope(vnode.elm, i)    }  }  function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {    for (; startIdx <= endIdx; ++startIdx) {      createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)    }  }  // 递归调用销毁钩子  function invokeDestroyHook (vnode) {    let i, j    const data = vnode.data    if (isDef(data)) {      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)    }    if (isDef(i = vnode.children)) {      for (j = 0; j < vnode.children.length; ++j) {        invokeDestroyHook(vnode.children[j])      }    }  }  /**   * 删除多个节点   * 文本节点能够间接删除, 其余节点须要触发两个钩子   */  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)        }      }    }  }  function removeAndInvokeRemoveHook (vnode, rm) {    if (isDef(rm) || isDef(vnode.data)) {      let i      const listeners = cbs.remove.length + 1      if (isDef(rm)) {        // we have a recursively passed down rm callback        // increase the listeners count        rm.listeners += listeners      } else {        // directly removing        rm = createRmCb(vnode.elm, listeners)      }      // recursively invoke hooks on child component root node      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {        removeAndInvokeRemoveHook(i, rm)      }      for (i = 0; i < cbs.remove.length; ++i) {        cbs.remove[i](vnode, rm)      }      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {        i(vnode, rm)      } else {        rm()      }    } else {      removeNode(vnode.elm)    }  }  // 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    // 在transition中 不能挪动节点    const canMove = !removeOnly    // 查看是否有反复的key    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)        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 {        // 以上四种状况都不满足时, 应用新列表第一个vdom的key去旧列表查找        // 如果能够找到key雷同的元素, 间接进行patch而后进入下一次循环        // 找不到则插入一个新节点        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)            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(parentElm, oldCh, oldStartIdx, oldEndIdx)    }  }  /**   * 查看是否有反复的key   * 一个很简略的遍历查找反复值的操作   * 其实这个seenKeys我感觉改成数组会更好, 写成object又给每个key的value置为true蛮奇怪的   */  function checkDuplicateKeys (children) {    const seenKeys = {}    for (let i = 0; i < children.length; i++) {      const vnode = children[i]      const key = vnode.key      if (isDef(key)) {        if (seenKeys[key]) {          warn(            `Duplicate keys detected: '${key}'. This may cause an update error.`,            vnode.context          )        } else {          seenKeys[key] = true        }      }    }  }  /**   * 在旧的子节点列表寻找类似节点(只查找第一个)   */  function findIdxInOld (node, oldCh, start, end) {    for (let i = start; i < end; i++) {      const c = oldCh[i]      if (isDef(c) && sameVnode(node, c)) return i    }  }  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {    // 如果oldVnode跟vnode完全一致,那么不须要做任何事件    if (oldVnode === vnode) {      return    }    const elm = vnode.elm = oldVnode.elm    if (isTrue(oldVnode.isAsyncPlaceholder)) {      if (isDef(vnode.asyncFactory.resolved)) {        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)      } else {        vnode.isAsyncPlaceholder = true      }      return    }    // 如果oldVnode跟vnode都是动态节点,且具备雷同的key    // 当vnode是克隆节点或是v-once指令管制的节点时    // 只须要把oldVnode.elm和oldVnode.child都复制到vnode上,也不必再有其余操作    // reuse element for static trees.    // note we only do this if the vnode is cloned -    // if the new node is not cloned it means the render functions have been    // reset by the hot-reload-api and we need to do a proper re-render.    if (isTrue(vnode.isStatic) &&      isTrue(oldVnode.isStatic) &&      vnode.key === oldVnode.key &&      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))    ) {      vnode.componentInstance = oldVnode.componentInstance      return    }    let i    const data = vnode.data    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {      i(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)      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)    }    // 如果vnode不是文本节点或正文节点    if (isUndef(vnode.text)) {      // 如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行updateChildren      if (isDef(oldCh) && isDef(ch)) {        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)      } else if (isDef(ch)) {        // 如果只有vnode有子节点,那就创立这些子节点        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)        // 如果只有oldVnode有子节点,那就把这些节点都删除      } else if (isDef(oldCh)) {        removeVnodes(elm, oldCh, 0, oldCh.length - 1)        // 如果oldVnode和vnode都没有子节点,然而oldVnode是文本节点或正文节点,就把vnode.elm的文本设置为空字符串      } else if (isDef(oldVnode.text)) {        nodeOps.setTextContent(elm, '')      }      // 如果vnode是文本节点或正文节点,然而vnode.text != oldVnode.text时,只须要更新vnode.elm的文本内容即可    } else if (oldVnode.text !== vnode.text) {      nodeOps.setTextContent(elm, vnode.text)    }    if (isDef(data)) {      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)    }  }  function invokeInsertHook (vnode, queue, initial) {    // delay insert hooks for component root nodes, invoke them after the    // element is really inserted    if (isTrue(initial) && isDef(vnode.parent)) {      vnode.parent.data.pendingInsert = queue    } else {      for (let i = 0; i < queue.length; ++i) {        queue[i].data.hook.insert(queue[i])      }    }  }  let hydrationBailed = false  // list of modules that can skip create hook during hydration because they  // are already rendered on the client or has no need for initialization  // Note: style is excluded because it relies on initial clone for future  // deep updates (#7063).  const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key')  // Note: this is a browser-only function so we can assume elms are DOM nodes.  function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {    let i    const { tag, data, children } = vnode    inVPre = inVPre || (data && data.pre)    vnode.elm = elm    if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {      vnode.isAsyncPlaceholder = true      return true    }    // assert node match    if (process.env.NODE_ENV !== 'production') {      if (!assertNodeMatch(elm, vnode, inVPre)) {        return false      }    }    if (isDef(data)) {      if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)      if (isDef(i = vnode.componentInstance)) {        // child component. it should have hydrated its own tree.        initComponent(vnode, insertedVnodeQueue)        return true      }    }    if (isDef(tag)) {      if (isDef(children)) {        // empty element, allow client to pick up and populate children        if (!elm.hasChildNodes()) {          createChildren(vnode, children, insertedVnodeQueue)        } else {          // v-html and domProps: innerHTML          if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {            if (i !== elm.innerHTML) {              /* istanbul ignore if */              if (process.env.NODE_ENV !== 'production' &&                typeof console !== 'undefined' &&                !hydrationBailed              ) {                hydrationBailed = true                console.warn('Parent: ', elm)                console.warn('server innerHTML: ', i)                console.warn('client innerHTML: ', elm.innerHTML)              }              return false            }          } else {            // iterate and compare children lists            let childrenMatch = true            let childNode = elm.firstChild            for (let i = 0; i < children.length; i++) {              if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {                childrenMatch = false                break              }              childNode = childNode.nextSibling            }            // if childNode is not null, it means the actual childNodes list is            // longer than the virtual children list.            if (!childrenMatch || childNode) {              /* istanbul ignore if */              if (process.env.NODE_ENV !== 'production' &&                typeof console !== 'undefined' &&                !hydrationBailed              ) {                hydrationBailed = true                console.warn('Parent: ', elm)                console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)              }              return false            }          }        }      }      if (isDef(data)) {        let fullInvoke = false        for (const key in data) {          if (!isRenderedModule(key)) {            fullInvoke = true            invokeCreateHooks(vnode, insertedVnodeQueue)            break          }        }        if (!fullInvoke && data['class']) {          // ensure collecting deps for deep class bindings for future updates          traverse(data['class'])        }      }    } else if (elm.data !== vnode.text) {      elm.data = vnode.text    }    return true  }  function assertNodeMatch (node, vnode, inVPre) {    if (isDef(vnode.tag)) {      return vnode.tag.indexOf('vue-component') === 0 || (        !isUnknownElement(vnode, inVPre) &&        vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())      )    } else {      return node.nodeType === (vnode.isComment ? 8 : 3)    }  }  /**   * 这里返回一个patch函数供后续对vnode进行patch操作   * 这里的patch操作是指, 将oldVnode对应的实在DOM更改为vnode对应的实在DOM, 所须要的最低性能开销的操作(或者说是较低)   * 参数中的oldVnode是更新前的旧节点, vnode是将要更新的新节点, hydrating是一个flag标识是否与原生DOM混合, removeOnly是在过渡动画中应用   */  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {    // 这里很简略, 如果新节点不存在, 旧节点也不存在, 无需任何操作, 如果新节点不存在,但旧节点存在, 阐明须要删除旧节点, 调用一个销毁钩子    if (isUndef(vnode)) {      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)      return    }    // 用于标识是否初始化这个节点    let isInitialPatch = false    const insertedVnodeQueue = []    // 旧节点不存在 阐明须要创立一个新节点    if (isUndef(oldVnode)) {      // empty mount (likely as component), create new root element      isInitialPatch = true      createElm(vnode, insertedVnodeQueue, parentElm, refElm)    } else {      // 走到这里 阐明新旧节点都存在, 这时比较复杂, 分几种状况解决       // 先通过nodeType判断是否是真正的节点, 真正的节点nodeType取值范畴是1~12      // vue里罕用的根本只有三种 1代表是dom元素节点 3是文本节点 8是正文节点      const isRealElement = isDef(oldVnode.nodeType)      if (!isRealElement && sameVnode(oldVnode, vnode)) {        // patch existing root node        // 惯例状况下, 新旧节点是类似节点, 对新旧节点做具体的比照操作        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)      } else {        if (isRealElement) {          // 当新旧节点不是类似节点, 旧节点是一个实在节点时          // mounting to a real element          // check if this is server-rendered content and if we can perform          // a successful hydration.          // 服务端渲染非凡解决          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {            oldVnode.removeAttribute(SSR_ATTR)            hydrating = true          }          // 须要用hydrate函数将虚构DOM和实在DOM进行映射          if (isTrue(hydrating)) {            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {              invokeInsertHook(vnode, insertedVnodeQueue, true)              return oldVnode            } else if (process.env.NODE_ENV !== 'production') {              warn(                'The client-side rendered virtual DOM tree is not matching ' +                'server-rendered content. This is likely caused by incorrect ' +                'HTML markup, for example nesting block-level elements inside ' +                '<p>, or missing <tbody>. Bailing hydration and performing ' +                'full client-side render.'              )            }          }          // either not server-rendered, or hydration failed.          // create an empty node and replace it          oldVnode = emptyNodeAt(oldVnode)        }        // replacing existing element        const oldElm = oldVnode.elm        const parentElm = nodeOps.parentNode(oldElm)        // create new node        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)        )        // update parent placeholder node element, recursively        if (isDef(vnode.parent)) {          let ancestor = vnode.parent          const patchable = isPatchable(vnode)          while (ancestor) {            for (let i = 0; i < cbs.destroy.length; ++i) {              cbs.destroy[i](ancestor)            }            ancestor.elm = vnode.elm            if (patchable) {              for (let i = 0; i < cbs.create.length; ++i) {                cbs.create[i](emptyNode, ancestor)              }              // #6513              // invoke insert hooks that may have been merged by create hooks.              // e.g. for directives that uses the "inserted" hook.              const insert = ancestor.data.hook.insert              if (insert.merged) {                // start at index 1 to avoid re-invoking component mounted hook                for (let i = 1; i < insert.fns.length; i++) {                  insert.fns[i]()                }              }            } else {              registerRef(ancestor)            }            ancestor = ancestor.parent          }        }        // destroy old node        if (isDef(parentElm)) {          removeVnodes(parentElm, [oldVnode], 0, 0)        } else if (isDef(oldVnode.tag)) {          invokeDestroyHook(oldVnode)        }      }    }    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)    return vnode.elm  }}

能够看到patch接管的参数

  1. oldVnode:旧的虚构节点
  2. vnode:新的虚构节点
  3. hydrating:是否映射
  4. removeOnly:标识
  5. parentElm:父节点
  6. refElm:被插入之后的占位符

那么外围diff代码在于 sameVnodecreateElmpatchVNode咱们顺次开展来说

sameVnode

顾名思义能够看判断两个节点是不是同一个节点

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)      )    )  )}/** * 节点 key 必须雷同 * tag、正文、data是否存在、input类型是否雷同 * 如果isAsyncPlaceholder是true,则须要asyncFactory属性雷同 */

createElm

// 创立一个节点  function createElm (    vnode,    insertedVnodeQueue,    parentElm,    refElm,    nested,    ownerArray,    index  ) {    // 节点曾经被渲染, 须要应用一个克隆节点    if (isDef(vnode.elm) && isDef(ownerArray)) {      // This vnode was used in a previous render!      // now it's used as a new node, overwriting its elm would cause      // potential patch errors down the road when it's used as an insertion      // reference node. Instead, we clone the node on-demand before creating      // associated DOM element for it.      vnode = ownerArray[index] = cloneVNode(vnode)    }    // 创立组件节点 详见本文件中的createComponent办法    vnode.isRootInsert = !nested // for transition enter check    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {      return    }    const data = vnode.data    const children = vnode.children    const tag = vnode.tag    /**     * 如果要创立的节点有tag属性, 这里做一下校验     * 如果该节点下面有v-pre指令, 间接给flag加1     * 如果没有v-pre须要调用isUnknownElement判断标签是否非法, 而后给出正告     */    if (isDef(tag)) {      if (process.env.NODE_ENV !== 'production') {        if (data && data.pre) {          creatingElmInVPre++        }        if (isUnknownElement(vnode, creatingElmInVPre)) {          warn(            'Unknown custom element: <' + tag + '> - did you ' +            'register the component correctly? For recursive components, ' +            'make sure to provide the "name" option.',            vnode.context          )        }      }      vnode.elm = vnode.ns        ? nodeOps.createElementNS(vnode.ns, tag)        : nodeOps.createElement(tag, vnode)      setScope(vnode)      /* istanbul ignore if */      if (__WEEX__) {        // in Weex, the default insertion order is parent-first.        // List items can be optimized to use children-first insertion        // with append="tree".        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)        if (!appendAsTree) {          if (isDef(data)) {            invokeCreateHooks(vnode, insertedVnodeQueue)          }          insert(parentElm, vnode.elm, refElm)        }        createChildren(vnode, children, insertedVnodeQueue)        if (appendAsTree) {          if (isDef(data)) {            invokeCreateHooks(vnode, insertedVnodeQueue)          }          insert(parentElm, vnode.elm, refElm)        }      } else {        createChildren(vnode, children, insertedVnodeQueue)        if (isDef(data)) {          invokeCreateHooks(vnode, insertedVnodeQueue)        }        insert(parentElm, vnode.elm, refElm)      }      if (process.env.NODE_ENV !== 'production' && data && data.pre) {        creatingElmInVPre--      }    } else if (isTrue(vnode.isComment)) {      vnode.elm = nodeOps.createComment(vnode.text)      insert(parentElm, vnode.elm, refElm)    } else {      vnode.elm = nodeOps.createTextNode(vnode.text)      insert(parentElm, vnode.elm, refElm)    }  }

此段代码就是创立实在dom的目标,下一章谈判到。

patchVnode

  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {    // 如果oldVnode跟vnode完全一致,那么不须要做任何事件    if (oldVnode === vnode) {      return    }    const elm = vnode.elm = oldVnode.elm    if (isTrue(oldVnode.isAsyncPlaceholder)) {      if (isDef(vnode.asyncFactory.resolved)) {        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)      } else {        vnode.isAsyncPlaceholder = true      }      return    }    // 如果oldVnode跟vnode都是动态节点,且具备雷同的key    // 当vnode是克隆节点或是v-once指令管制的节点时    // 只须要把oldVnode.elm和oldVnode.child都复制到vnode上,也不必再有其余操作    // reuse element for static trees.    // note we only do this if the vnode is cloned -    // if the new node is not cloned it means the render functions have been    // reset by the hot-reload-api and we need to do a proper re-render.    if (isTrue(vnode.isStatic) &&      isTrue(oldVnode.isStatic) &&      vnode.key === oldVnode.key &&      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))    ) {      vnode.componentInstance = oldVnode.componentInstance      return    }    let i    const data = vnode.data    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {      i(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)      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)    }    // 如果vnode不是文本节点或正文节点    if (isUndef(vnode.text)) {      // 如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行updateChildren      if (isDef(oldCh) && isDef(ch)) {        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)      } else if (isDef(ch)) {        // 如果只有vnode有子节点,那就创立这些子节点        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)        // 如果只有oldVnode有子节点,那就把这些节点都删除      } else if (isDef(oldCh)) {        removeVnodes(elm, oldCh, 0, oldCh.length - 1)        // 如果oldVnode和vnode都没有子节点,然而oldVnode是文本节点或正文节点,就把vnode.elm的文本设置为空字符串      } else if (isDef(oldVnode.text)) {        nodeOps.setTextContent(elm, '')      }      // 如果vnode是文本节点或正文节点,然而vnode.text != oldVnode.text时,只须要更新vnode.elm的文本内容即可    } else if (oldVnode.text !== vnode.text) {      nodeOps.setTextContent(elm, vnode.text)    }    if (isDef(data)) {      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)    }  }

具体代码性能曾经解释的很分明了,这里的addVnodesremoveVnodes就是新增与移除虚构节点,外围代码咱们次要关注一个updateChildren

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    // 在transition中 不能挪动节点    const canMove = !removeOnly    // 查看是否有反复的key    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)        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 {        // 以上四种状况都不满足时, 应用新列表第一个vdom的key去旧列表查找        // 如果能够找到key雷同的元素, 间接进行patch而后进入下一次循环        // 找不到则插入一个新节点        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)            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(parentElm, oldCh, oldStartIdx, oldEndIdx)    }  }

这里利用了while循环与双指针比照新旧虚构dom

定义指针变量

  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] // 新列表起点值

定义循环

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 {        // 以上四种状况都不满足时, 应用新列表第一个vdom的key去旧列表查找        // 如果能够找到key雷同的元素, 间接进行patch而后进入下一次循环        // 找不到则插入一个新节点        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)            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]      }    }

检测oldStartVnode、oldEndVnode

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

如果oldStartVnode不存在,oldCh起始点向后挪动。如果oldEndVnode不存在,oldCh终止点向前挪动。

oldStartVnode 和 newStartVnode 是雷同节点

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

如果oldStartVnodenewStartVnode 是雷同节点,则patchVnode,同时彼此向后挪动一位

oldEndVnode 和 newEndVnode 是雷同节点

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

如果oldEndVnodenewEndVnode 是雷同节点,则patchVnode,同时彼此向前挪动一位

oldStartVnode 和 newEndVnode 是雷同节点

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]}

如果oldStartVnodenewEndVnode 是雷同节点,则先 patchVnode,而后把oldStartVnode移到oldCh最初的地位即可,而后oldStartIdx向后挪动一位,newEndIdx向前挪动一位

oldEndVnode 和 newStartVnode 是雷同节点

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]}

如果oldEndVnodenewStartVnode 是雷同节点,则先 patchVnode,而后把oldEndVnode移到oldCh最前的地位即可,而后newStartIdx向后挪动一位,oldEndIdx向前挪动一位

key不雷同执行createElm办法

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)}

如果以上条件都不匹配,则查找oldVnode中与vnode具备雷同key的节点,并将查找的后果赋值给elmToMove。如果找不到雷同key的节点,则示意是新创建的节点

key雷同,就认为是同一节点

vnodeToMove = oldCh[idxInOld]if (sameVnode(vnodeToMove, newStartVnode)) {  patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)  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)}newStartVnode = newCh[++newStartIdx]

若为同一类型就调用patchVnode,就将对应下标处的oldVnode设置为undefined,把vnodeToMove插入到oldCh之前,newStartIdx持续向后挪动。如果两个 vnode 不雷同,视为新元素,执行 createElm创立。

如果老dom的开始索引大于完结索引,新dom数组大于老dom数组,示意新增会调用addVnodes办法

if (oldStartIdx > oldEndIdx) {  refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm  addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)}

如果老dom的开始索引小于完结索引,新dom数组小于老dom数组,示意新增会调用removeVnodes办法

else if (newStartIdx > newEndIdx) {  removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)}

总结

因为古代diff算法策略同层级比拟,广度优先,故而古代算法复杂度为O(n) 这一章咱们讲述了传统diff算法复杂度,O(n^3)到古代的O(n)的实现的一个思路,下一章就开始解说比照过后的vdom如何映射实在dom