最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue源码解析-patch&diff算法

    正文概述 掘金(老刘大话前端)   2021-04-05   791

    在vue中,patch过程,是以新的虚拟dom为基准,改造旧的虚拟dom。

    宏观上讲,patch过程就做了3件事:

    • 创建节点
    • 更新节点
    • 删除节点

    接下来,我们逐个击破。

    一. update

    在执行render函数,返回虚拟dom之后,vue会执行update方法,去更新视图。其主干代码如下:

    
     Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
       const vm: Component = this
       const prevEl = vm.$el
       const prevVnode = vm._vnode
       
       const restoreActiveInstance = setActiveInstance(vm)
       vm._vnode = vnode
       
       if (!prevVnode) {
         vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
       } else {
         vm.$el = vm.__patch__(prevVnode, vnode)
       }
       restoreActiveInstance()
       
       // ...
     }
     
    
    

    setActiveInstance

     export let activeInstance: any = null
    
     export function setActiveInstance(vm: Component) {
       const prevActiveInstance = activeInstance
       activeInstance = vm
       return () => {
         activeInstance = prevActiveInstance
       }
     }
    

    前面章节,我们分析了组件化实践。setActiveInstance方法是 设置 当前是 哪个组件被激活。 因为 同一时间,只会有一个组件实例化。

    activeInstance变量是 当前正在实例化的组件对象。 prevActiveInstance实际上是父的实例化对象。在每次子组件实例化并且patch之后,就会执行restoreActiveInstance方法,就会将当前的 activeInstance 重置为 当前的父组件,以此类推,直到最上层的Vue。

    需要指出的是, 这里设置了 activeInstance, 会在 组件实例化的 时候 会使用到, 不清楚的小伙伴可以看我的上一篇 《Vue源码解析-组件化&虚拟DOM》

    下面,我们继续看__patch__

    二. patch

    __patch__方法的定义,实际就是执行的 createPatchFunction 方法。此方法比较庞大,我们先看主入口patch方法定义:

    return function patch(oldVnode, vnode, hydrating, removeOnly) {
      // ...
      
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      }else {
        if(isRealElement) {
          // ssr 属性... 暂时忽略
          
          oldVnode = emptyNodeAt(oldVnode)
        }
        
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        createElm(
          vnode, 
          insertedVnodeQueue, 
          oldElm._leaveCb ? null : parentElm, 
          nodeOps.nextSibling(oldElm)
        )
        
        if (isDef(vnode.parent)) {
          // ...
        }
      }
      
      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
      return vnode.elm
    }
    

    我们先来看个?:

    <html>
      <head>
        <meta charset="utf-8"/>
      </head>
    
      <body>
        <div id='root'></div>
    
        <script src="../vue/dist/vue.js"></script>
        <script>
    
          let vm = new Vue({
            el: '#root',
            data() {
              return {
                a: "这是根节点"
              }
            },
            template: `<div data-test='这是测试属性' @click="handleClick"> {{ a }} </div>`,
    
            methods: {
              handleClick() {
                this.a = '变了'
              }
            },
    
          })
        </script>
      </body>
    </html>
    

    页面渲染时,会执行一次update patch。

    oldVnode

    此时oldVnode是div#root,是实际的DOM节点。

    vnode值是执行render函数得到,其结构大致如下:

    vnode

    {
      tag: "div",
      text: undefined,
      key: undefined,
      isStatic: false,
      isRootInsert: true,
      isComment: false,
      elm: undefined,
      componentInstance: undefined,
      componentOptions: undefined,
      children: [
        // vnode  纯文本节点
        {
          // ...
        }
      ],
      context: Vue,
      data: {
        attrs: {...},
        on: {
          click: function () {...}
        }
      }
    }
    

    nodeType

    nodeType实际上是html的原生属性,这里第一次渲染时,nodeType为节点, 值为 1。

    不清楚nodeType的小伙伴,可以移步: www.w3school.com.cn/jsref/prop_…

    回归到我们的demo中,isRealElement = 1, 显示是true。这个时候会调用 emptyNodeAt

    emptyNodeAt

    function emptyNodeAt (elm) {
      return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
    }
    

    问题来了,第一次页面渲染时,oldVnode是id = "root"的真实dom节点。为什么需要调用emptyNodeAt方法,重新设置为虚拟dom节点?

    Vue源码解析-patch&diff算法

    其实有几点原因:

      1. removeVnodes是基于虚拟dom操作
      1. invokeDestroyHook也是基于虚拟dom操作
      1. 新旧节点diff对比,都是基于虚拟dom操作

    此时,根节点root转化为虚拟dom之后(即oldVnode),其数据结构如下:

    {
      tag: "div",
      text: undefined,
      key: undefined,
      isStatic: false,
      isRootInsert: true,
      isComment: false,
      elm: undefined,
      componentInstance: undefined,
      componentOptions: undefined,
      children: [],
      context: Vue,
      data: {},
      // 注意此变化
      elm: div#root
    }
    

    到这里,页面还没渲染时,只有一个空div,id = 'root', vue将其转化为vnode,其上面的oldVnode空节点 和 new Vue之后的vnode做对比。

    Vue源码解析-patch&diff算法

    需要指出的是: parentElm 在第一次update时,指的是body

    三. createElm

    function createElm (
      vnode,
      insertedVnodeQueue,
      parentElm,
      refElm,
      nested,
      ownerArray,
      index
    ) {
      // ...
      
      // 嵌套组件处理
      if(createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
        return
      }
      
      // ...
      
      const tag = vnode.tag
      if(isDef(tag)) {
          vnode.elm = vnode.ns
            ? nodeOps.createElementNS(vnode.ns, tag)
            : nodeOps.createElement(tag, vnode);
            
          setScope(vnode)
      
          if(__WEEX__) {
            // ... weex相关处理
          }else {
            createChildren(vnode, children, insertedVnodeQueue)
            if (isDef(data)) {
              invokeCreateHooks(vnode, insertedVnodeQueue)
            }
            insert(parentElm, vnode.elm, refElm)
          }
      }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)
      }
    } 
    

    createComponent实现多层嵌套组件,这里不再赘述。不清楚的小伙伴,可以看我之前的《vue源码解析-组件化&虚拟DOM》。

    第一次渲染时,触发createElm方法,传入的vnode即新的vnode。显然,在我们的demo中,tag = "div",ns = undefined。 将会执行nodeOps.createElement方法。

    nodeOps对象,实际上就是封装了对原生DOM的操作。 这里createElement方法,实际上就是调用:document.createElement方法,返回原生dom对象。

    此时新的虚拟dom上elm属性,指向就是刚创建的div。此时 vnode数据结构如下:

    {
      tag: "div",
      text: undefined,
      key: undefined,
      isStatic: false,
      isRootInsert: true,
      isComment: false,
      // elm属性值变更为 刚创建的 div dom对象
      elm: div,
      componentInstance: undefined,
      componentOptions: undefined,
      children: [
        // vnode  纯文本节点
        {
          // ...
        }
      ],
      context: Vue,
      data: {
        attrs: {...},
        on: {
          click: function () {...}
        }
      }
    }
    

    需要注意的是:setScope(vnode),实际上是对于elm真实dom的style对象,添加scopeId。

    到这里,demo中的外层div已创建,但是这个时候还是没有文字显示。因为children中的文字,也是个虚拟dom,只不过他是普通纯文本节点而已。

    下面流程,将调用 createChildren 方法。

    四. createChildren

    其主干代码如下:

    function createChildren (vnode, children, insertedVnodeQueue) {
      // ...
      
      for (let i = 0; i < children.length; ++i) {
        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
      }
      // ...
    }
    

    这里,我们可以看到,实际上是个递归循环操作。 无论我们的组件嵌套多少层,都将对每层vnode的childrens进行循环。然后一个个createElm,遇到childrens,继续调用createChildren。如此反复,递归一个个创建子组件。

    在我们的demo中,子的childrens是一行文本,属于纯文本节点。那么createElm时,将进入最后一个else操作,创建文本节点。即原生的dom调用:

    document.createTextNode(text)
    

    同理,子的vnode对象上的elm属性,指向了刚刚创建的文本节点的真实dom 对象。

    最后调用update restoreActiveInstance方法,激活当前的父组件为 当前的activeInstance实例。

    嗯,这只是个最简单的patch过程,还未涉及多层嵌套和对比。

    因为这是第一次渲染过程,而diff是发生已渲染页面的情况下,再次发生页面需要变更。

    下面,我们将进入数据变化,视图需要变化的patch过程

    五. patchVnode

    reactiveSetter

    前面的章节介绍了依赖收集,我们知道,当数据改变时,会触发reactiveSetter。

    首先reactiveSetter 会判断,前后的value是否相同,如果相同直接return。 否则进入下面的环节。

    依赖收集时,Dep类的实例对象dep下有个subs数组,里面存放了依赖这些数据的watcher对象。

    所以当触发reactiveSetter时,实际上是调用了每个watcher的update方法。

    watcher的update方法,并不是直接去更新。而是将watcher放入一个更新队列里。

    注意: 这个更新队列的大小,最大是100个

    最后调用nextTick函数,设置promise更新队列,在callback中执行Scheduler job,即每个watcher的run方法。最终将进入第二轮patch。

    需要注意的是:为什么要有队列?这其实是两方面考虑:

    • 性能考虑,因为同一个nextTick里,可能同一个组件,依赖了多个数据对象,而多个数据对象都变化了,没必要update多次,在队列中,vue会判断是否属于同一个watcher id。
    • 多个组件,分别依赖了多个数据对象。每个组件,实际上都会有自己的nextTick。

    这里实际上远不止如此,后面我将单独开一个章节,分享更新队列和nextTick。

    此时,oldVnode数据结构如下:

    oldVnode

    {
      tag: "div",
      text: undefined,
      key: undefined,
      isStatic: false,
      isRootInsert: true,
      isComment: false,
      elm: div,
      componentInstance: undefined,
      componentOptions: undefined,
      children: [
        {
          tag: undefined,
          text: "这是根节点",
          key: undefined,
          isStatic: false,
          isRootInsert: false,
          isComment: false,
          elm: test,
          componentInstance: undefined,
          componentOptions: undefined,
          children: undefined,
          // ...
        }
      ],
      context: Vue,
      data: {
        attrs: {...},
        on: {
          click: function () {...}
        }
      },
      // ...
    }
    

    vnode (新的vnode)

    {
      tag: "div",
      text: undefined,
      key: undefined,
      isStatic: false,
      isRootInsert: true,
      isComment: false,
      elm: div,
      componentInstance: undefined,
      componentOptions: undefined,
      children: [
        {
          tag: undefined,
          // 注意,这里变了
          text: "变了",
          key: undefined,
          isStatic: false,
          isRootInsert: false,
          isComment: false,
          elm: test,
          componentInstance: undefined,
          componentOptions: undefined,
          children: undefined,
          // ...
        }
      ],
      context: Vue,
      data: {
        attrs: {...},
        on: {
          click: function () {...}
        }
      },
      // ...
    }
    

    不难看出,此次isRealElement = false,将先执行sameVnode判断。

    我们先看sameVnode做了些什么

    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的判断,有没有很熟悉? 这也就是我们写数组循环时,需要加key的原因。

    sameInputType方法,其实很简单:

      1. 如果不是input节点,直接返回true
      1. 如果是,那么判断虚拟dom上的data, attrs, type是否相等

    下面终于进入了 patchVnode 方法:

    主干代码如下:

    function patchVnode (
      oldVnode,
      vnode,
      insertedVnodeQueue,
      ownerArray,
      index,
      removeOnly
    ) {
      if (oldVnode === vnode) {
        return
      } 
      
      // ...
      
      // ... 省略异步占位组件
      
      if (isTrue(vnode.isStatic) &&
          isTrue(oldVnode.isStatic) &&
          vnode.key === oldVnode.key &&
          (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
      ) {
          vnode.componentInstance = oldVnode.componentInstance
          return
      }
      
      // ...
      // 组件节点,需要先调用组件prepatch钩子,data,props,slot,listener等可能都需要更新
      // ...此处省略
      
      if (isUndef(vnode.text)) {
          if (isDef(oldCh) && isDef(ch)) {
            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
          } else if (isDef(ch)) {
            if (process.env.NODE_ENV !== 'production') {
              checkDuplicateKeys(ch)
            }
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
          } else if (isDef(oldCh)) {
            removeVnodes(oldCh, 0, oldCh.length - 1)
          } else if (isDef(oldVnode.text)) {
            nodeOps.setTextContent(elm, '')
          }
        } else if (oldVnode.text !== vnode.text) {
          nodeOps.setTextContent(elm, vnode.text)
        }
    }
    

    可以看到,这里是虚拟dom前后对比更新的情况。大致分以下几种:

      1. 先判断oldVnode和vnode,是否相等,如果相等,return
      1. 如果oldVnode是静态节点,并且vnode也是静态节点。并且,oldVnode.key 和 vnode.key相等。并且,vnode节点是克隆的或者是isOnce,那么直接返回,不需要对比了。
      1. 如果新的vnode,不是文本节点,那么:
      • 3.1 如果oldVnode和vnode都存在children,那么:

        • 3.1.1 如果2个children不相等,那么updateChildren (这里比较复杂,需要单独分析)
      • 3.2 如果新的vnode存在children, 而老的oldVnode不存在children,那么:

        • 3.2.1 如果老的oldVnode是文本节点,那么先清空真实dom中的内容,再把新的vnode的children添加到真实dom中。

        • 3.2.2 如果老的oldVnode不是文本节点,那么直接添加到DOM中

      • 3.3 如果新的vnode不存在children,而老的oldVnode中存在children,那么:直接把dom中的子节点清空

      • 3.4 如果新的vnode,老的oldVnode都不存在children,但是老的oldVnode是文本节点,那么直接清空DOM内容

      1. 如果新的vnode是文本节点,老的oldVnode也是文本节点,那么:如果内容不相等,用新的内容覆盖老的内容

    updateChildren

    上面,3.1.1情况,如果新老vnode,都存在children,但是他们不相等,那么将调用updateChildren方法。这里单独说明。

    其实,都有children的情况下,也不外乎四种处理方式,分别是:

      1. 创建子节点
      1. 删除子节点
      1. 移动子节点
      1. 更新子节点

    Vue源码解析-patch&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
    
        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 {
                
                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)
        }
      }
    

    Vue源码解析-patch&diff算法

    可以看到,updateChildren阶段,实际上是将新的vnode和老的oldVnode,进行双重循环。

    如下:

    开始

    Vue源码解析-patch&diff算法

    第二步

    Vue源码解析-patch&diff算法

    第三步

    Vue源码解析-patch&diff算法

    第四步

    Vue源码解析-patch&diff算法

    第五步

    Vue源码解析-patch&diff算法

    第六步

    Vue源码解析-patch&diff算法

    diff的比较,实际上,都是以新的vnode为基准,不断的调整老的vnode位置。

    可以看到diff的比较策略, 左左,右右,左右,右左

    六. 总结

    patch第一阶段:activeInstance

      1. 同一时刻,只会有一个组件正在实例化和patch
      1. 设置当前被实例化的activeInstace对象,并且保留preActiveInstace。
      1. 当前保留的activeInstace,在patch过程中,遇到嵌套组件,需要做为其parent,进行组件的实例化。
      1. 当前子组件patch完成后,将切换当前的activeInstance为preActiveInstance,如果还有多层嵌套的话,再次重复上面的过程。

    patch第二阶段:root dom patch

      1. dom根即 div#root容器,页面首页渲染时,oldVnode不是一个虚拟dom,而是一个真实dom。此时oldVnode.nodeType = 1,
      1. 将div#root本身,转化为空的虚拟dom。将其作为oldVnode与新的vnode进行对比。
      1. 进入createElm,那么:
      • 3.1 如果存在组件,那么进行组件化patch (组件化不了解的小伙伴可以看我的上一篇)
      1. 完成insert,注意此时并没有进入patchVnode diff。
      1. 设置dom style scope id

    patch第三阶段:reactiveSetter

      1. 页面完成了首次渲染,如果页面上有数据变化了,将触发reactiveSetter。(依赖收集不清楚的小伙伴,可以看我之前的分享:《vue源码解析-响应式原理》)
      1. 对比新老数据是否相等,如果相等,直接return
      1. 数据不相等,将根dep下的subs,循环调用watcher的update方法。
      1. watcher的update,并不是直接去通知更新。 而是放在一个队列中。更新通知将进入queueWatcher
      1. queueWatcher中优化不必要的多次渲染,比如:多个值的变化,都指向同一个watcher,没必要触发多次patch
      1. Scheduler job中,将调用watcher的run方法
      1. 执行render函数,获取新的vnode,执行update,重复前1个阶段。
      1. isRealElement 为undefined,进入 patchVnode阶段

    patch第四阶段:patchVnode

      1. 比较新老节点,是否相等。即oldVnode == vnode。如果相等,直接return
      1. 是否是静态节点,是否前后key相等,或者 是否是克隆节点/isOnce。是直接return。(备注:静态标记是compiler第二阶段生成的)
      1. 如果是比较的是组件节点,那么根据vnode更新oldVnode组件props, listener, slots, parent等属性
      1. 新的vnode是文本节点,那么:
      • 4.1 如果oldVnode和vnode 都存在childrens,
        • 4.1.1 如果2个children相等,那么直接return
        • 4.1.2 如果2个children不相等,那么只需第5阶段-updateChildren
      • 4.2 如果新的节点存在children, 而老的节点不存在children,那么:
        • 4.2.1 如果老的节点是文本节点,那么先清空老的子节点内容
        • 4.2.1 将新的vnode的多个children,插入到老的dom流中
      • 4.3 如果新的节点不存在children,而老的节点存在children,那么:
        • 4.3.1 将老的childrens全部删除
      • 4.4 如果老节点,新节点都不存在children,并且老的节点是文本节点,那么清空老的节点内容
      1. 新老节点都是文本节点,但是文件节点内容不同,那么直接用新的文本内容 更新 老的文本内容

    patch第五阶段:updateChildren

      1. 同层比较,不同层的节点是不能复用的
      1. oldStartVnode指的是未处理的开始节点,newStartVnode新的未处理的开始节点
      1. oldEndVnode指的是未处理的最后节点,newEndVnode新的未处理的最后节点
      1. 比较策略:oldStartVnode 和 newStartVnode 先比较,那么:
      • 4.1 如果相等,那么将 oldStartVnode,newStartVnode 都往后挪一个
      • 4.2 如果不相等,那么进入 oldEndVnode, newEndVnode 比较
      1. oldEndVnode 和 newEndVnode 比较,那么:
      • 5.1 如果相等,那么将 oldEndVnode 和 newEndVnode 都往前挪一个
      • 5.2 如果不相等,那么进入 oldStartVnode 和 newEndVnode
      1. oldStartVnode 和 newEndVnode 比较,那么:
      • 6.1 如果相等,那么将 oldStartVnode 向后挪一个,将 newEndVnode向前挪一个。
      • 6.2 如果不相等,将进入 oldEndVnode 和 newStartVnode 比较
      1. oldEndVnode 和 newStartVnode 比较,那么:
      • 7.1 如果相等,将 oldEndVnode 往前挪一个,newStartVnode 往后挪一个
      • 7.2 如果不相等,那么将进入 查找节点
      1. 根据新的vnode位置,去同层的老节点中查找。
      • 8.1 如果存在,那么移动到对应的位置(注意,是未处理节做参照物,而不是已处理节点)
      • 8.2 如果不存在,那么根据新的节点children,创建节点,放入老的节点之中
      • 8.3 如果老的节点,在新的节点中不存在,那么将老的对应的节点删除
      1. 这就是双指针算法,如此循环,就能将所有节点对比完成。总的概括,不外乎三点:
      • 9.1 同层不存在,直接更新移动
      • 9.2 同层不存在,那么创建
      • 9.3 新的节点,同层 在 老节点中 不存在,那么删除

    以上,就是patch阶段的总体流程。

    码字不易,多多关注?


    起源地下载网 » Vue源码解析-patch&diff算法

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元