diff 的时机
当组件创建、以及依赖的属性或数据变化从而运行_update()
函数时。
_update 的作用
- 接收一个
vnode
参数,即新的 vnode。 - 同时通过当前组件的
_vnode
属性拿到旧的 vnode。 - 将新的 vnode 赋值给 _vnode 属性。
- 判断旧的 vnode 是否存在:
- 不存在则调用
patch
函数遍历新的 vnode 为每个节点创建真实 DOM,并挂载到每个节点的ele
属性上。 - 存在则通过 patch 函数对新旧 vnode 进行对比,让新 vnode 的每个节点的 ele 属性匹配到合适的真实 DOM,并以最小代价完成真实 DOM 处理。
- 不存在则调用
patch 函数的运行流程
主要是通过同时满足以下 3 个条件判断两个虚拟节点是否相同(相同三要素):
- 标签类型
tag
是否相同 key
值是否相同- 如果是 input 元素,
type 属性
是否相同
对比过程总结为深度优先,同层对比
,即先将当前节点及其子节点对比完,再从同层节点寻找下个对比对象,详细流程:
-
根节点比较
- 若相同,则开始更新,将旧节点的真实 dom 赋值给新节点的 ele 属性,对比新旧节点的属性,将变化更新到真实 dom 中,当前两个节点处理完毕后开始对比子节点。
- 若不相同,则新节点递归创建真实 dom,旧节点销毁 ele 属性,即销毁旧的真实 dom。
-
对比子节点,大致与上述对比相同,通过以下方面进行对比效率提升
- 优先只改动真实 dom 元素属性
- 其次移动元素以替代创建和删除元素
- 最后才考虑创建和删除元素
子节点对比细节
vue 对新旧 vnode 生成了新旧两个子节点数组,并对两个数组同使用
两个指针
,分别指向各自数组的头和尾,然后不断向数组中间靠拢来一一对比:若新旧数组的头或尾指针所指向的节点相同(相同三要素),则只改动真实 dom 元素的属性差异,同时当前两个指针移动。
若不同则从旧数组中寻找所有与当前新数组头指针相同的节点,若还没找到,则创建元素,同时新数组头或尾指针移动,进行下一轮对比...当新数组头指针超过尾指针,循环结束,没有被匹配到的旧数组会被遍历删除元素。
就这样一直递归遍历下去,直到整颗新的 vnode tree 对比完成,之后旧的 vnode 所占用的内存空间则会被垃圾回收。