每次修改或新增数据都要创建渲染一整个真实dom的话要开销大量性能,所以vue用diff算法来实现局部更新。
真实dom和虚拟dom
渲染完真实dom以后,会根据真实dom生成一个虚拟dom
当树上某个VNODE节点的数据发生改变后,会生成一个新的虚拟dom
把新旧两个虚拟dom进行对比,把两者的差异更新到真实dom上并渲染这个需要更新的部分,这样就避免了重新渲染一整棵树
然后把旧的虚拟dom删除,新的虚拟dom就成为了下一次要进行对比的旧虚拟dom
虚拟dom的对比过程对树上的节点VNODE进行了遍历对比,而且只会进行同层的比较,先把每一层的比较完才会去对比下一层次的节点
patch
函数
vue认为在不看后代节点的情况下(也就是先不看下一层),两个节点如果key和tag都相同,那这两个节点就是相等的
patch函数就是在这种情况下,对两个节点进行判断
如果不相同,就没有往下一层比较的必要了,把真实dom上的对应的节点的树结构直接替换成新的VNODE(这样做可以尽可能减少比较的次数)
如果相同,就进行更深层的对比,执行patchVnode函数
patchVnode
函数
更新的过程中会根据新旧VNODE有子节点(children)还是有文本(text)来做不同的操作,总共有4种情况:
- 旧VNODE有子节点,新VNODE没有:直接删掉真实dom的子节点
- 旧VNODE没有子节点,新VNODE有子节点:创建新VNODE的子节点,添加到真实dom上
- 新/旧VNODE都有文本节点:如果文本内容不同,直接把真实dom的文本改成新VNODE的文本
- 新/旧VNODE都有子节点:把两者的children数组单独拎出来对比,对比的过程如下
updateChildren
函数
对于这两个子节点数组,会分别声明两个指针指向头尾
在头尾指针不错位的情况下(即头指针始终不到尾指针的右边,尾指针始终不到头指针的左边)
四个头尾指针指向的子节点的对比分成5种
-
旧VNODE的头指针和新VNODE的头指针,相等的话指针向中间靠拢,把对应的节点移到相应位置
-
旧VNODE的头指针和新VNODE的尾指针,相等的话指针向中间靠拢,把对应的节点移到相应位置
-
旧VNODE的尾指针和新VNODE的头指针,相等的话指针向中间靠拢,把对应的节点移到相应位置
-
旧VNODE的尾指针和新VNODE的尾指针,相等的话指针向中间靠拢,把对应的节点移到相应位置
- 如果以上4种情况都不符合
拿到新VNODE的头指针,把整个旧VNODE的子节点数组进行遍历,目的是尽可能找到相等的节点,减少创建新节点
- 如果找到了,就对这个节点进行移动
- 如果遍历结束还没找到,就不得不创建,创建后根据新VNODE插入对应位置
一旦新/旧VNODE的头尾指针错位了,那就意味着这个结点数组遍历完成
如果新节点比旧结点先遍历完,把真实dom的多余节点删除
如果旧结点比新节点先遍历完,把剩余的新节点创建并添加到真实dom中
图解如下:(假设旧VNODE子节点为[1, 2, 3, 4, 5]
,新VNODE子节点为[1, 2, 6, 5, 3, 4]
)
- 对比两个头指针指向的节点,发现相同,把对应的节点移到相应位置,指针向右走一步
- 对比两个头指针指向的节点,发现相同,把对应的节点移到相应位置,指针向右走一步
- 前四种比较没有找到相同的,以新VNODE子节点的头指针指向的节点为基准,遍历旧VNODE子节点,发现没找到,创建并插入到正确位置
- 对比旧的尾指针和新的头指针,发现相同,把对应的节点移到相应位置,指针向中间走一步
-
对比两个头指针指向的节点,发现相同,把对应的节点移到相应位置,指针向右走一步
-
对比两个头/尾指针指向的节点,发现相同,把对应的节点移到相应位置,指针再走下去就错位了,结束对比