Diff 算法核心原理
1、认识虚拟 DOM(Virtual DOM)
没有 vue 之前我们都是用 js 和 jq,操作 DOM—>视图更新
有了 vue,数据改变—>生成虚拟 DOM(计算变更)—>操作真实的 DOM—>视图更新
真实 DOM:
|
对应的虚拟 DOM 为:
|
虚拟 DOM 算法 = 虚拟 DOM + Diff 算法
好处:虚拟 DOM 算法操作真实 DOM,性能高于直接操作真实 DOM
2、什么是 Diff 算法

上图中,其实只有一个 li 标签修改了文本,其他都是不变的,所以没必要所有的节点都要更新,只更新这个 li 标签就行,Diff 算法就是查出这个 li 标签的算法。
总结:Diff 算法是一种对比算法。对比两者是旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点更改了,找出这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,而不用更新其他数据没发生改变的节点,实现精准地更新真实 DOM,进而提高效率。
3、Diff 算法的原理
新旧虚拟 DOM 对比的时候,Diff 算法比较只会在同层级进行, 不会跨层级比较。


4、Diff 对比流程

当数据改变时,会触发setter,并且通过Dep.notify去通知所有订阅者Watcher,订阅者们就会调用patch方法

1、Data 通过 Observer 转换成了 getter/setter 的形式来追踪变化。
2、当外界通过 Watcher 读取数据时,会触发 getter 从而将 Watcher 添加到依赖中。
3、当数据发生了变化时,会触发 setter,从而 Dep 中的依赖(Watcher)发送通知。
4、Watcher 接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能会触发用户返回的某个回调函数。
patch 方法
对比当前同层的虚拟节点是否为同一种类型的标签:
- 是:继续执行
patchVnode方法进行深层比对 - 否:没必要比对了,直接整个节点替换成
新虚拟节点

patch的核心原理代码:
|
sameVnode 方法
patch 关键的一步就是sameVnode方法判断是否为同一类型节点,
sameVnode 方法的核心原理代码:
|
patchVnode 方法
这个函数做了以下事情:
- 找到对应的
真实DOM,称为el - 判断
newVnode和oldVnode是否指向同一个对象,如果是,那么直接return - 如果他们都有文本节点并且不相等,那么将
el的文本节点设置为newVnode的文本节点。 - 如果
oldVnode有子节点而newVnode没有,则删除el的子节点 - 如果
oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化之后添加到el - 如果两者都有子节点,则执行
updateChildren函数比较子节点,这一步很重要
|
updateChildren 方法
updateChildren 的核心原理代码:
|
首尾指针法:

会进行互相进行比较,总共有五种比较情况:
1、
oldS 和 newS使用sameVnode方法进行比较,sameVnode(oldS, newS)2、
oldS 和 newE使用sameVnode方法进行比较,sameVnode(oldS, newE)3、
oldE 和 newS使用sameVnode方法进行比较,sameVnode(oldE, newS)4、
oldE 和 newE使用sameVnode方法进行比较,sameVnode(oldE, newE)5、如果以上逻辑都匹配不到,再把所有旧子节点的
key做一个映射到旧节点下标的key -> index表,然后用新vnode的key去找出在旧节点中可以复用的位置。图例比较:

第一步:oldS 和 newE 相等,需要把节点a移动到newE所对应的位置,也就是末尾

第二步:oldS 和 newS相等,需要把节点b移动到newS所对应的位置

第三步:oldS、oldE 和 newS相等,需要把节点c移动到newS所对应的位置

第四步:oldCh先遍历完成了,而newCh还没遍历完,说明newCh比oldCh多,所以需要将多出来的节点,插入到真实 DOM 上对应的位置上

如果oldCh比newCh多的话,那就是newCh先走完循环,然后oldCh会有多出的节点,结果会在真实 DOM 里进行删除这些旧节点。
5、为什么不建议用 index 作为循环项的 key

a,b,c三个li标签都是复用之前的,因为他们三个根本没改变,改变的只是前面新增了一个林三心
在进行子节点的 diff算法 过程中,会进行 旧首节点和新首节点的sameNode对比,这一步命中了逻辑,因为现在新旧两次首部节点 的 key 都是 0了,同理,key 为 1 和 2 的也是命中了逻辑,导致相同key的节点会去进行patchVnode更新文本,而原本就有的c节点,却因为之前没有 key 为 4 的节点,而被当做了新节点,所以使用 index 做 key,最后新增的居然是本来就已有的 c 节点。所以前三个都进行patchVnode更新文本,最后一个进行了新增,那就解释了为什么所有 li 标签都更新了。

我们只要使用一个独一无二的值 id 来当做 key 就行了。

- 本文作者: luckyship
- 本文链接: https://luckyship.github.io/2021/09/12/2021-09-12-diff-calculate/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
