在生成vnode之后,可以通过patch方法创建DOM元素、进行diff更新DOM元素、销毁DOM元素

1.首先是第一次初始化时,生成了vnode后,在_update方法中调用patch

vm.$el = vm.__patch__(
  vm.$el, vnode, hydrating, false
  // undefined
  vm.$options._parentElm,
  // undefined
  vm.$options._refElm
);

此时vm.$el是挂载的根元素,vnode是根元素对应的虚拟Dom元素

patch方法

  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    // 如果更新后的VNode不存在
    // 直接销毁旧的节点
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return
    }

    var isInitialPatch = false;
    var insertedVnodeQueue = [];
		
    if (isUndef(oldVnode)) {
      // 如果旧节点不存在
      // 创建VNode对应的元素
      isInitialPatch = true;
      createElm(vnode, insertedVnodeQueue, parentElm, refElm);
    } else {
      // oldValue是否是真实的DOM元素
      var isRealElement = isDef(oldVnode.nodeType);
      // 新旧节点相同
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
      } else {
        if (isRealElement) {
          // oldVNode类型为元素类型
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR);
            hydrating = true;
          }
      		......
          // 创建一个div空元素
          oldVnode = emptyNodeAt(oldVnode);
        }
				// oldElm代表真实的DOM元素
        var oldElm = oldVnode.elm;
        var parentElm$1 = nodeOps.parentNode(oldElm);

        createElm(
          vnode,
          insertedVnodeQueue,
          oldElm._leaveCb ? null : parentElm$1,
          nodeOps.nextSibling(oldElm)
        );
				......
        if (isDef(parentElm$1)) {
          removeVnodes(parentElm$1, [oldVnode], 0, 0);
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode);
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
  }
}

其中调用了一个重要的方法createElm

  function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
		......
    // VNode如果是一个组件,调用createComponent
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    var data = vnode.data;
    var children = vnode.children;
    var tag = vnode.tag;
    // 如果tag是一个字符串,但非平台标签,也非自定义组件,抛错
    if (isDef(tag)) {
      if (process.env.NODE_ENV !== 'production') {
        if (data && data.pre) {
          creatingElmInVPre++;
        }
        if (isUnknownElement$$1(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
          );
        }
      }
			
      // setScope用户设置scoped CSS
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode);
      setScope(vnode);

        var appendAsTree = isDef(data) && isTrue(data.appendAsTree);
        if (!appendAsTree) {
          if (isDef(data)) {
            // 调用invokeCreateHooks
            // 此方法会处理directives、ref、attrs、class、domProps、on、style和show等一些属性
            // 如果VNode上有对应的狗子函数则会直接执行
            // 如果有insert方法,则把VNode添加到insertedVnodeQueue数组
            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);
        }
      }

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

在初始化时,parentElm指的是bodyrefElm是当前节点的下一个兄弟元素

2.上面说的是Vue第一次加载页面时patch的操作,还有当页面绑定的数据修改后,Vue对页面的更新,其核心也就是diff算法了 在页面绑定的数据发生改变时,watcher会调用updateComponent,然后调用vm._render生成最新的vnode, 然后vm._update会讲新旧vnode传入patch方法中进行diff处理,其实总体流程还是一样,只不过传入的参数不同

return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
  ......

  let isInitialPatch = false;
  const insertedVnodeQueue = [];

  if (isUndef(oldVnode)) {
    isInitialPatch = true;
    createElm(vnode, insertedVnodeQueue, parentElm, refElm);
  } else {
     // oldValue是不是vnode真实的dom元素
    const isRealElement = isDef(oldVnode.nodeType);
    // 判断是否为同一vnode
    // 如果是统一vnode,调用patchVnode
    // sameVnode判断依据为两个vnode的key相同,tag名相同,type,data,attrs如果有的话必须相同
    // 如果可以复用,patchVnode
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
    } else {
      ......
    }
  }

  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
  return vnode.elm
}

patchVnode


function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  // 如果新旧vnode相同,return
  if (oldVnode === vnode) {
    return
  }                           
  // vnode应的dom指向oldVnode的dom             
  const elm = vnode.elm = oldVnode.elm;

  ......

  // 如果新旧节点都是静态根节点
  // key也相同
  // 如果renderStatic或者markOnce
  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;
  // 调用prepatch钩子函数
  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
    i(oldVnode, vnode);
  }

  // 新旧vnode children
  const oldCh = oldVnode.children;
  const ch = vnode.children;
  // data不为空,即节点相关的属性不为空
  // isPatchable用于判断vnode的tag是否为空
  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);
  }
  // 判断vnode是否为文本节点
  if (isUndef(vnode.text)) {
    // 新旧节点都有子元素
    if (isDef(oldCh) && isDef(ch)) {
      // 子元素不相同
      // 调用updateChildren
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
      // 如果新的vnode有子元素,旧的没有
    } else if (isDef(ch)) {
      // 如果旧节点是文本节点,则置空
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '');
      // addVnodes把ch中的元素依次添加到elm中
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      // 如果新节点vnode没有子元素
    } else if (isDef(oldCh)) {
      // 删除旧节点的子元素
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      // 以上如果都不满足,如果oldVnode是文本结点,则直接内容置空
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, '');
    }
    // 如果vnode是文本结点,且text有变化,则修改elm的文本内容
  } else if (oldVnode.text !== vnode.text) {
    nodeOps.setTextContent(elm, vnode.text);
  }
  // 调用postpatch钩子函数
  if (isDef(data)) {
    if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode);
  }
}

接下来重点是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, vnodeToMove, refElm

  // removeOnly为false,transition-group中为true
  const canMove = !removeOnly

  if (process.env.NODE_ENV !== 'production') {
    checkDuplicateKeys(newCh)
  }
  // 新旧节点都未遍历完
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 如果oldStartVnode未定义
    if (isUndef(oldStartVnode)) {
      // oldCh向后移一位
      oldStartVnode = oldCh[++oldStartIdx]
      // 如果oldEndVnode未定义
    } else if (isUndef(oldEndVnode)) {
      // oldCh尾索引向前移动一位
      oldEndVnode = oldCh[--oldEndIdx]
      // 首节点比较,如果能复用
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      // 调用patchVnode,更新oldStartVnode、newStartVnode,也会递归updateChildren
      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)) {
      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)) {
      patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
      canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
      oldEndVnode = oldCh[--oldEndIdx]
      newStartVnode = newCh[++newStartIdx]
    } else {
      // 以上条件都不满足,调用createKeyToOldIdx
      // createKeyToOldIdx 方法的作用,遍历oldCh,找到里面设置key的对象
      // 返回一个map,key为键,index为值
      if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
      // 查询新节点在oldCh中的索引
      idxInOld = isDef(newStartVnode.key) ?
        oldKeyToIdx[newStartVnode.key] :
        findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
      // 如果没找到,说明是新建的节点
      if (isUndef(idxInOld)) {
        // 创建新的DOM元素,插入到指定节点
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
      } else {
        // 如果在oldCh中找到了索引
        vnodeToMove = oldCh[idxInOld]
        // 可复用
        if (sameVnode(vnodeToMove, newStartVnode)) {
          // patchVnode复用dom元素递归子元素
          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
          // vnode处理完了之后清楚oldCh对应的节点
          oldCh[idxInOld] = undefined
          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          // 不可复用,直接创建新元素
        } else {
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        }
      }
      // newCh向后移一位
      newStartVnode = newCh[++newStartIdx]
    }
  }
  // 如果oldCh遍历完了
  if (oldStartIdx > oldEndIdx) {
    // 创建剩下的dom节点,添加到对应的位置
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    // 如果newCh遍历完了,oldCh没有 
    // 移出剩下的oldCh
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
}

这就是diff处理vnode children属性的过程

3.对于自定义组件,在createElm方法中中,会调用一个方法createComponent来判断是否是自定义组件

...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
	return
}
...

createComponent

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

调用init初始化,init方法定义在componentVNodeHooks中,componentVNodeHooks中定义的钩子函数,会在patch中调用

const componentVNodeHooks = {
  init (
    vnode,
    hydrating,
    parentElm,
    refElm
  ) {
    // 如果当前vnode上没有组件实例或者已经销毁,则创建新的新的component实例
    if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
      // 调用createComponentInstanceForVnode
      // createComponentInstanceForVnode方法中会处理组件中的各种属性
      // 比如 Ctor是自定义组件的构造函数,propsData是父组件通过props传递的数据
      // listeners是添加在当前组件上的事件,tag是自定义的标签名,children即当前自定义组件的子元素
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance,
        parentElm,
        refElm
      );
      // 调用$mount
      // 其内部依旧调用__patch__
      // 然后update
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    } else if (vnode.data.keepAlive) {
      const mountedNode = vnode;
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    }
  },
  // patchVNode中调用
  // 组件diff之前的操作
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    // 调用updateChildComponent,通过传入的属性,更新对应的模板,数据,事件
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },

  // patch invokeInsertHook中调用
  // 生成的dom元素插入页面后调用
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    // 设置_isMounted为true,调用mounted钩子函数
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      // keepAlive组件,如果已经加载好了的
      // 调用queueActivatedComponent,放进activatedChildren
      if (context._isMounted) {
        queueActivatedComponent(componentInstance)
      } else {
        // 反之调用activateChildComponent,触发activated钩子函数
        // 这又是keepAlive专有的生命周期
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },

  destroy (vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        // 销毁组件
        componentInstance.$destroy()
      } else {
        // 对于keepAlive组件,调用deactivateChildComponent,触发deactivated钩子函数
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}

销毁组件destroy

Vue.prototype.$destroy = function () {
  const vm: Component = this
  if (vm._isBeingDestroyed) {
    return
  }
  // 调用beforeDestroy钩子函数
  callHook(vm, 'beforeDestroy')
  vm._isBeingDestroyed = true
  const parent = vm.$parent
  // 从父元素中删除当前元素
  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
    remove(parent.$children, vm)
  }
  // 销毁watcher
  if (vm._watcher) {
    vm._watcher.teardown()
  }
  // _watchers减一
  let i = vm._watchers.length
  while (i--) {
    vm._watchers[i].teardown()
  }
  // observe减一
  if (vm._data.__ob__) {
    vm._data.__ob__.vmCount--
  }
  vm._isDestroyed = true
  // 传入null,销毁当前组件
  vm.__patch__(vm._vnode, null)
  // 调用destroyed钩子函数
  callHook(vm, 'destroyed')
  // 销毁事件
  vm.$off()
  // 消除各种属性
  if (vm.$el) {
    vm.$el.__vue__ = null
  }
  vm.$options._parentElm = vm.$options._refElm = null
}

以上就是patch的内容,包括一次生成dom元素,diff更新操作,还有对于component先转换为vnode再进行patch,另外还有一些生命周期函数