Vue源码 虚拟Dom总结

总体过程中,在Vue首次渲染过后再updateComponent()这个方法中调用了vm._render()和vm._update()这两个方法

一、vm._render()

在render方法里面它去调用了用户传过来的render函数或者是通过模板编译生成render函数,如果调用了用户传过来的render函数那么接下来调用的是vm.$createElement这个方法(它就是h函数),如果是模板编译成成的render函数它调用的是_c这个方法(它就是h函数),但是不管是哪个方法它最终调用的都是createElement(vm,a,b,c,d,true),在createElement(vm,a,b,c,d,true),它去处理函数传入参数的差异,最终调用的_createElement(),在_createElement()这个方法中最终创建了VNode对象 (vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context))最终vm._render()结束返回一个vnode,就是VNode对象,返回来的vnode对象交给vm._update()处理

二、vm._update()

vm._update()这个方法负责把虚拟dom渲染成真实dom,用于渲染的就是调用了vm._patch_方法,在首次执行时,vm._patch_方法传入的第一个参数是vm._$el,传入的是真实dom,如果是数据更新的时候vm._patch_()方法传入的是两个vnode,第一个参数是preVnode也就是上一次渲染时保存的一个vnode

三、vm._patch_()

vm._patch_()方法实在runtime/index.js中去初始化的,在初始化的过程中他是在Vue的原型上挂载了_patch_方法 Vue.prototype._patch_ ,所有的实例都可以访问到,这个vm._patch_()方法等同于runtime/patch.js文件夹下导出的patch函数,在这个函数中主要设置了两个对象,分别是modules和nodeOps,modules里存放的是和web平台相关或无关的模板,nodeOps这个对象里面存放的是用来操作dom的,当设置好这两个对象后调用createPatchFunction()函数,在这个函数中最终返回了所需要的的patch函数

四、patch()

patch()函数是在vdom/patch.js中的createPatchFunction()函数中返回的,在createPatchFunction()这个函数中首先做了一个初始化的事情,它的里面定义了特别多的辅助函数,并且去初始化了cbs这个对象,cbs这个对象中存放了所有模块中定义的钩子函数,这些钩子函数的作用是用来处理所有节点的属性/事件/样式等操作,并且在patch()这个函数中还会去判断我们传入的第一个参数是否是真实dom,如果是真实dom的话证明是首次加载这个时候就会把我们的真实dom转化为vnode,然后去调用createElm将newVnode转化为真实dom并挂载到dom树上来,如果是数据更新的时候,新旧节点都是vnode,这时候通过sameVnode这个方法判断新旧vnode是否是相同vnode相同节点,如果是相同节点的话执行patchVnode也就是diff算法,在执行过patchVnode后回去删除旧的节点,在patch()函数中有两个核心函数一个是createElm(vnode,insertVnodeQueue)和patchVnode

五、createElm(vnode, insertedVnodeQueue)

这个函数的作用是将虚拟dom转换成真实dom,并且挂载到dom树上,并且它还将虚拟节点的子节点转换成真实dom挂载到dom树,并且触发相应的钩子函数

六、patchVnode

patchVnode的作用是对比新旧VNode并且对比新旧VNode的子节点,更新差异,如果新旧VNode都有子节点且新旧子节点不同的话,会调用updateChildren对比子节点差异

七、updateChildren

从头和尾依次找到相同的子节点进行比较patchNode,总共有四种方式 ,如果四种方式都不符合会在老节点的子节点中查找newStartVnode,并进行处理,当循环结束后如果新节点比老节点多,就把新增的子节点插入到DOM中,如果老节点比新节点多,把多余的老节点删除diff算法

// 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 
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(newCh)
    }
    // diff 算法
    // 当新节点和旧节点都没有遍历完成
    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)) {
        // oldStartVnode 和 newStartVnode 相同(sameVnode)
        // 直接将该 VNode 节点进行 patchVnode
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        // 获取下一组开始节点
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 直接将该 VNode 节点进行 patchVnode
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        // 获取下一组结束节点
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // oldStartVnode 和 newEndVnode 相同(sameVnode)
        // 进行 patchVnode,把 oldStartVnode 移动到最后
        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
        // oldEndVnode 和 newStartVnode 相同(sameVnode)
        // 进行 patchVnode,把 oldEndVnode 移动到最前面
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // 以上四种情况都不满足
        // newStartNode 依次和旧的节点比较
        
        // 从新的节点开头获取一个,去老节点中查找相同节点
        // 先找新开始节点的key和老节点相同的索引,如果没找到再通过sameVnode找
        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]
          // 如果使用 newStartNode 找到相同的老节点
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // 执行 patchVnode,并且将找到的旧节点移动到最前面
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // 如果key相同,但是是不同的元素,创建新元素
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    // 当结束时 oldStartIdx > oldEndIdx,旧节点遍历完,但是新节点还没有
    if (oldStartIdx > oldEndIdx) {
      // 说明新节点比老节点多,把剩下的新节点插入到老的节点后面
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      // 当结束时 newStartIdx > newEndIdx,新节点遍历完,但是旧节点还没有
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }

 

你可能感兴趣的:(Vue源码)