
1.Vue的diff算法是什么?
diff算法是一种通过同层的树节点比较的高效算法。其有两个特点:
- 比较只会在同层级进行,不会跨层级比较
 
- 在diff比较的过程中,循环从两边向中间比较
 
diff算法在很多场景下都有应用,在vue中,作用于虚拟dom渲染成真实dom的新旧VNode节点比较
2.Vue的diff算法的比较方式是什么?
diff算法的整体策略为:深度优先,同层比较。
- 只会在同层级进行,不会跨层级比较

 
- 在比较过程中,循环会从两边向中间收拢

 
下面举个vue通过diff算法更新的例子:
新旧节点如下图所示:

第一次循环之后发现旧节点D(old - endIndex)和新节点D(new - startIndex)相同,直接复用旧节点D作为diff后的第一个真实节点,同时旧节点的endIndex往前移动到C,新节点的startIndex移动到C

第二次循环,发现此时旧节点的endIndex对应的节点C与新节点的startIndex对应的节点C相同,故此进行和第一次循环相同的工作

第三次循环发现新节点E未出现在旧节点中,只能直接创建真实节点E插入到C节点之后,然后将新节点的startIndex移动到下一个节点A上

第四次循环发现旧节点的startIndex对应的节点值A和新节点startIndex对应的节点值A相同,故将A节点移动到真实节点E之后。此时同时将旧节点的startIndex后移一位,新节点的startIndex后移一位

第五次循环发现旧节点的startIndex对应的节点B与新节点startIndex对应的节点B相同,所以将节点B移动到真实节点A之后,同时新旧节点的startIndex都后移一位

第六次循环发现旧节点的startIndex的值大于endIndex值,所以就需要创建新节点中所有startIndex到endIndex的节点,放在真实节点B之后

3.Vue的diff算法的原理分析
在Vue中当数据发生变化时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实DOM打补丁,更新相应的视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | function patch(oldVnode, vnode, hydrating, removeOnly) {     if (isUndef(vnode)) {          if (isDef(oldVnode)) invokeDestroyHook(oldVnode)         return     }
      let isInitialPatch = false     const insertedVnodeQueue = []
      if (isUndef(oldVnode)) {         isInitialPatch = true         createElm(vnode, insertedVnodeQueue)      } else {         const isRealElement = isDef(oldVnode.nodeType)         if (!isRealElement && sameVnode(oldVnode, vnode)) {                          patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)         } else {                          if (isRealElement) {
                  if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {                     oldVnode.removeAttribute(SSR_ATTR)                     hydrating = true                 }                 if (isTrue(hydrating)) {                     if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {                         invokeInsertHook(vnode, insertedVnodeQueue, true)                         return oldVnode                     }                 }                 oldVnode = emptyNodeAt(oldVnode)             }             return vnode.elm         }     } }
 
  | 
 
patch函数前两个参数为oldVNode和VNode,分别代表新的节点和之前的旧节点,函数中主要做了四个判断:
1️⃣ 没有新节点,直接触发旧节点的destory钩子
2️⃣ 没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较,直接调用createElm全部新建
3️⃣ 旧节点和新节点一样:通过sameVnode判断节点是否一样,一样直接调用patchVnode去处理这两个节点
4️⃣ 旧节点和新节点自身不一样:当两个节点不一样时,直接创建新节点,删除旧节点
接下来,谈一谈patchVnode部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
   | function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {          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     }                    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)     }          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, '')       }
                    } 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)     }   }
 
  | 
 
patchVnode主要做以下4个判断:
1️⃣ 新节点是否为文本节点,如果是,直接更新dom的文本内容为新节点的文本内容
2️⃣ 新节点和旧节点如果都有子节点,则调用updateChildren函数处理比较更新子节点
3️⃣ 只有新节点有子节点,旧节点没有,不需要比较,所有子节点都是全新的,直接全部新建(创建出新的DOM,添加进老节点里作为子节点)
4️⃣ 只有旧节点有子节点而新节点没有子节点,要做的就是将所有的旧节点的子节点全部删除
小结
- 当Vue中的数据发生变化时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者会调用patch函数给真实DOM打补丁
 
- 在patch函数中通过isSameVnode方法进行判断新旧节点是否相同,相同则调用patchVnode方法
 
- patchVnode方法主要做了以下操作:
 
- 
 
- 如果新节点均为文本节点,且只有文本内容不同,直接更新旧节点的文本内容即可
 
 
- 如果旧节点有子节点而新节点没有子节点,将旧节点的所有子节点删除就OK了
 
 
- 如果新节点有子节点而旧节点没有子节点,则创建新节点的子节点添加到旧节点里作为子节点
 
 
- 新旧节点都有子节点,调用updateChildren函数比较子节点  
 
 
- updateChildren函数主要做以下操作:
 
- 
 
- 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch工作、调用createElm创建新节点等
 
 
