-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vue Diff Algorithm #10
Comments
简单 Diff 算法
|
简单 Diff 算法的弊端
双端 Diff 算法的优点
|
双端 Diff 算法双端 Diff 算法在一次对比中,对 2 个新 VDom 和 2 个旧 VDom 分别进行横向和交叉对比 const newVDoms = [
{ key: 2, type: 'p', children: 'n-0' },
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' },
]
// 1. 第一次对比: newStartIndex = 0, newEndIndex = 2, oldStartIndex = 0, oldEndIndex = 2
// a. newVDoms[0] & oldVDoms[0]
// b. newVDoms[2] & oldVDoms[2]
// c. newVDoms[2] & oldVDoms[0]
// d. newVDoms[0] & oldVDoms[2]
// 第 a,b,c 种情况,VDom 的 Key 不同,表示不可以复用,则不做任何操作
// 第 d 种情况下两个 VDom 的 Key 相同,表示可以复用,则更新节点,因为 d 属于交叉对比,则需要移位
// 所有操作结束后,更新所有索引。因为刚才操作的是 newStartIndex & oldEndIndex 位置上的索引
// 所以对 newStartIndex 加 1,对 oldEndIndex 减 1,故 newStartIndex = 1, newEndIndex = 2, oldStartIndex = 0, oldEndIndex = 1
// 2. 第二次对比: newStartIndex = 1, newEndIndex = 2, oldStartIndex = 0, oldEndIndex = 1
// a. newVDoms[1] & oldVDoms[0]
// b. newVDoms[2] & oldVDoms[1]
// c. newVDoms[1] & oldVDoms[1]
// d. newVDoms[2] & oldVDoms[0]
// 第 c,d 种情况,VDom 的 Key 不同,表示不可以复用,则不做任何操作
// 第 a,b 种情况下两个 VDom 的 Key 相同,表示可以复用,则更新节点即可
// 所有操作结束后,更新索引。因为刚才操作的是 newStartIndex & newEndIndex & oldStartIndex & oldEndIndex 位置上的索引
// 所以对 newStartIndex & oldStartIndex 加 1,对 newEndIndex & oldEndIndex 减 1,故 newStartIndex = 2, newEndIndex = 1, oldStartIndex = 1, oldEndIndex = 0
// 因为 newEndIndex > newStartIndex & oldStartIndex > oldEndIndex,所以结束循环 极端情况下的双端 Diff 算法每次对比时,不是一定会出现可复用的 VDom,需要一些额外的操作,来打破这种窘境 const newVDoms = [
{ key: 2, type: 'p', children: 'n-0' },
{ key: 3, type: 'p', children: 'o-3' },
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' },
{ key: 3, type: 'p', children: 'o-3' },
]
// 1. 第一次对比: newStartIndex = 0, newEndIndex = 3, oldStartIndex = 0, oldEndIndex = 3
// a. newVDoms[0] & oldVDoms[0]
// b. newVDoms[3] & oldVDoms[3]
// c. newVDoms[3] & oldVDoms[0]
// d. newVDoms[0] & oldVDoms[3]
// 会发现这四种对比,发现可以复用的 VDom
// 2. 额外的操作: 遍历 oldVDoms,找到可以复用的 VDom(索引为 tarIndex),更新对应元素后,把 oldVDom[tarIndex] 置为 undefined,然后 newStartIndex 加 1
const newVDoms = [
{ key: 2, type: 'p', children: 'n-0' },
{ key: 3, type: 'p', children: 'o-3' }, // newStartIndex
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' }, // newEndIndex
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' },
undefined,
{ key: 3, type: 'p', children: 'o-3' }, // oldEndIndex
]
// 3. 第二次对比: newStartIndex = 1, newEndIndex = 3, oldStartIndex = 0, oldEndIndex = 3 新增 VDom// 第一次对比
const newVDoms = [
{ key: 4, type: 'p', children: 'n-0' }, // newStartIndex
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' },
{ key: 2, type: 'p', children: 'n-3' }, // newEndIndex
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' }, // oldEndIndex
]
// 第二次对比
const newVDoms = [
{ key: 4, type: 'p', children: 'n-0' }, // newStartIndex
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' }, // newEndIndex
{ key: 2, type: 'p', children: 'n-3' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' }, // oldEndIndex
{ key: 2, type: 'p', children: 'o-2' },
]
// 第三次对比
const newVDoms = [
{ key: 4, type: 'p', children: 'n-0' }, // newStartIndex
{ key: 0, type: 'p', children: 'n-1' }, // newEndIndex
{ key: 1, type: 'p', children: 'n-2' },
{ key: 2, type: 'p', children: 'n-3' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex oldEndIndex
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' },
]
// 第四次对比
const newVDoms = [
{ key: 4, type: 'p', children: 'n-0' }, // newStartIndex newEndIndex
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' },
{ key: 2, type: 'p', children: 'n-3' },
]
const oldVDoms = [ // oldEndIndex
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' },
]
// oldVDoms 已经遍历完毕,所以索引 newStartIndex 对应的元素应该被新增 删除 VDom// 第一次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-1' }, // newStartIndex
{ key: 1, type: 'p', children: 'n-2' }, // newEndIndex
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' }, // oldEndIndex
]
// 第二次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' }, // newStartIndex newEndIndex
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' }, // oldStartIndex
{ key: 2, type: 'p', children: 'o-2' }, // oldEndIndex
]
// 第三次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' }, // newEndIndex
] // newStartIndex
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' }, // oldStartIndex oldEndIndex
]
// newVDoms 已经遍历完毕,所以索引 oldStartIndex 对应的元素应该被删除 |
快速 Diff 算法前置 VDom 和后置 VDom这是快速 Diff 算法的预处理阶段,借鉴文本 Diff 算法,对 VDoms 的前缀元素和后缀元素进行 DOM 更新,这个过程中不会涉及 DOM 移位 // 前置 VDom 的对比: 从第一个 VDom 向前遍历
// 前置 VDom 的第一次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-0' }, // newStartIndex
{ key: 2, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' },
]
// 前置 VDom 的第二次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-0' },
{ key: 2, type: 'p', children: 'n-1' }, // newStartIndex
{ key: 1, type: 'p', children: 'n-2' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' }, // oldStartIndex
]
// 此时 newVDoms[newStartIndex].key !== oldVDoms[oldStartIndex].key
// 所以停止遍历 // 后置 VDom 的对比: 从最后一个 VDom 向前遍历
// 后置 VDom 的第一次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-0' },
{ key: 2, type: 'p', children: 'n-1' },
{ key: 1, type: 'p', children: 'n-2' }, // newEndIndex
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' }, // oldEndIndex
]
// 后置 VDom 的第二次对比
const newVDoms = [
{ key: 0, type: 'p', children: 'n-0' },
{ key: 2, type: 'p', children: 'n-1' }, // newEndIndex
{ key: 1, type: 'p', children: 'n-2' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldEndIndex
{ key: 1, type: 'p', children: 'o-1' },
]
// 此时 newVDoms[newEndIndex].key !== oldVDoms[oldEndIndex].key
// 所以停止遍历 新增和删除 VDom前面的代码示例中,最后的索引对比结果是 VDom 移位快速 Diff 算法借助 newVDoms 索引表和最长递增子序列完成移位判断 const newVDoms = [
{ key: 0, type: 'p', children: 'n-0' }, // newStartIndex
{ key: 3, type: 'p', children: 'n-1' },
{ key: 4, type: 'p', children: 'n-2' },
{ key: 2, type: 'p', children: 'n-3' },
{ key: 7, type: 'p', children: 'n-4' },
{ key: 5, type: 'p', children: 'n-5' }, // newEndIndex
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' }, // oldStartIndex
{ key: 1, type: 'p', children: 'o-1' },
{ key: 2, type: 'p', children: 'o-2' },
{ key: 3, type: 'p', children: 'o-3' },
{ key: 4, type: 'p', children: 'o-4' },
{ key: 5, type: 'p', children: 'o-5' }, // oldEndIndex
]
// 1. 判断前置 VDom 和 后置 VDom
const newVDoms = [
{ key: 0, type: 'p', children: 'n-0' },
{ key: 3, type: 'p', children: 'n-1' }, // newStartIndex
{ key: 4, type: 'p', children: 'n-2' },
{ key: 2, type: 'p', children: 'n-3' },
{ key: 7, type: 'p', children: 'n-4' }, // newEndIndex
{ key: 5, type: 'p', children: 'n-5' },
]
const oldVDoms = [
{ key: 0, type: 'p', children: 'o-0' },
{ key: 1, type: 'p', children: 'o-1' }, // oldStartIndex
{ key: 2, type: 'p', children: 'o-2' },
{ key: 3, type: 'p', children: 'o-3' },
{ key: 4, type: 'p', children: 'o-4' }, // oldEndIndex
{ key: 5, type: 'p', children: 'o-5' },
]
// 后面的逻辑,不包括已经更新的前缀 VDom 和后缀 VDom
// 2. 建立 newVDoms 的索引表: { [newVDom.key]: newVDoms.indexof(VDom) }
// => { 3:0, 4:1, 2:2, 7:3 }
// 3. 建立 source 数组: [ oldVDoms.indexof(newVDom.key) ]
// => [2, 3, 1, -1]
// 4. 根据 source 数组,计算最长递增子序列
// [2, 3, 1, -1] => [2, 3] => [0, 1]
// 5. 遍历最长递增子序列与 oldVDoms.slice(oldStartIndex, oldEndIndex + 1)
// 所以索引为 oldStartIndex 和 oldStartIndex + 1 的两个 oldVDom 不用进行移位
// 又因为 source 数组的最后一项是 -1,即 oldVDoms 中不存在此 newVDom
// 故索引为 oldStartIndex + 2 的 oldVDom 需要进行移位
// 故索引为 newStartIndex + 3 的 newVDom 需要进行新增 |
概述
Vue Diff 算法的迭代历程:简单 Diff 算法 => 双端 Diff 算法 => 快速 Diff 算法
简单 Diff 算法思路简单,但它的 VDom 移位策略性能较差,双端 Diff 算法和快速 Diff 算法是通过修改 VDom 的对比策略来追求极致性能,双端 Diff 算法使用平行和交叉对比的方式来进行移位的判断,所以需要大量的对比,不是较好的解决办法,快速 Diff 算法借助最长递增子序列,仅需少量对比即可得知需要移位的 VDom 。在此基础上,快速 Diff 算法借鉴文本 Diff 算法对 VDom 进行预处理,以减少 VDom 的对比范围
The text was updated successfully, but these errors were encountered: