1、diff比较算法

图示:

vue diff算法 patch_VUE

diff比较只会在同层级进行, 不会跨层级比较。

所以diff是:广度优先算法。

 

时间复杂度:O(n)

 vue diff算法 patch_javascript_02

 

代码示例:



<!-- 之前 -->
<div> <!-- 层级1 -->
<p> <!-- 层级2 -->
<b> aoy </b> <!-- 层级3 -->
<span>diff</Span>
</P>
</div>

<!-- 之后 -->
<div> <!-- 层级1 -->
<p> <!-- 层级2 -->
<b> aoy </b> <!-- 层级3 -->
</p>
<span>diff</Span>
</div>


我们可能期望将​​<span>​​直接移动到​​<p>​​的后边,这是最优的操作。

但是实际的diff操作是:

(1)移除​​<p>​​里的​​<span>​

(2)创建一个新的​​<span>​​插到​​<p>​​的后边。

因为新加的​​<span>​​在层级2,旧的在层级3,属于不同层级的比较。

 

一般的diff算法中都采用的是深度优先遍历。对新旧两棵树进行一次深度优先的遍历,这样每个节点都会有一个唯一的标记。在遍历的时候,每遍历到一个节点就把该节点和新的树的同一个位置的节点进行对比,如果有差异的话就记录到一个对象里面。

vue diff算法 patch_javascript_03

 例如,上面的div和新的div有差异,当前的标记是0,那么:patches[0] = [{difference}, {difference}, …]。同理p是patches[1],ul是patches[3],以此类推。这样当遍历完整棵树的时候,就可以获得一个完整的差异对象。

 

vue源码中会有一个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)
)
)
)
}


表示2个Vnode是否是同一个节点:

(1)当是一样的节点,直接复用(若设置key的话)

(2)当不是一样的节点的话,新节点直接替换老节点。

 

2、比较原则

图示:

vue diff算法 patch_VUE_04

图师说明:

粉红色的部分为oldNode,黄色的表示newNode。

vue diff算法 patch_patch_05

s和e指针指向它们的头child和尾child Node的指针

现在分别对​​oldS、oldE、S、E​​两两做​​sameVnode​​比较,有四种比较方式。

即:

oldS == S?

oldS == E?

oldE == S?

oldE == E?

 

diff算法:

  • 如果是oldS和E匹配上了,那么真实dom中的第一个节点会移到最后
  • 如果是oldE和S匹配上了,那么真实dom中的最后一个节点会移到最前,匹配上的两个指针向中间移动
  • 如果四种匹配没有一对是成功的,那么遍历​​oldChild​​,​​S​​挨个和他们匹配,匹配成功就在真实dom中将成功的节点移到最前面,如果依旧没有成功的,那么将​​S对应的节点​​插入到dom中对应的​​oldS​​位置,​​oldS​​和​​S​​指针向中间移动。

 

3、key

不设key,newCh和oldCh只会进行头尾两端的相互比较。

设key后,除了头尾两端的比较外,还会从用key生成的对象​​oldKeyToIdx​​中查找匹配的节点,所以为节点设置key可以更高效的利用dom。

(1)下图是没有设置key的diff算法

vue diff算法 patch_代码示例_06

(2)下图是有设置key的diff算法

vue diff算法 patch_代码示例_07