更新element的children,两端diff算法3
- 中间对比 顺序调整
- 暴力排序 新节点里面的元素在老节点里面存在不存在,存在的话就依次插入到新的位置
- 找到递增序列(下面案例中 c d 是递增序列) 剩下的元素进行移动
- 判断是否是递增序列,不是的话就进行移动
- 上面的方案可行,我们找到最长递增子序列,剩下的元素进行移动,让不需要移动的元素尽可能的保持不动
- 案例
- a b ( c d e ) f g < -------- >
- a b ( e c d ) f g
- 情况分析
- 初始化中间对比时的数组的值
js
const newIndexToOldIndexMap = new Array(toBePatched) // ✅ 初始化中间需要调整位置的数组下标,值都统一设置为0
for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0 // ✅
if (newIndex === undefined) { // 新节点里面没有,就把老节点删除
hostPatchRemove(prevChild.el)
} else { // 新节点里面有就进行对比渲染
newIndexToOldIndexMap[newIndex - s2] = i + 1 // ✅ 初始化数组的下标
patch(prevChild, c2[newIndex], container, parentComponent, null)
patched++
}
- 我们直接复制最长递增子序列的函数到
renderer.ts文件,直接求出想要的最长递增子序列的下标
js
function getSequence(arr) {
const p = arr.slice();
const result = [0];
let i, j, u, v, c;
const len = arr.length;
for (i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== 0) {
j = result[result.length - 1];
if (arr[j] < arrI) {
p[i] = j;
result.push(i);
continue;
}
u = 0;
v = result.length - 1;
while (u < v) {
c = (u + v) >> 1;
if (arr[result[c] as any] < arrI) {
u = c + 1;
} else {
v = c;
}
}
if (arrI < arr[result[u] as any]) {
if (u > 0) {
p[i] = result[u - 1];
}
result[u] = i;
}
}
}
u = result.length;
v = result[u - 1];
while (u-- > 0) {
result[u] = v;
v = p[v];
}
return result;
}
js
// 生成最长子序列
const increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
// 创建一个j 作为最长递增子序列的指针
let j = 0
for(let i = 0; i < toBePatched; i++){
if(i !== increasingNewIndexSequence[j]) {
console.log('移动位置');
} else {
j++
}
}
-
移动位置如何进行移动呢?
-
思路:我们可以设置 insert 方法
-
比如新数组为 a b e c d f g, e c d 属于中间乱序部分
-
我们 e 要插在 c 的前面,但 c 的位置还没有固定,且如果 c 也属于不稳定元素,那不就不能确定 e 的位置了?
-
所以我们需要倒序处理中间的元素,当我们处理 d 时,哎?f属于中间乱序之外的元素,且可以确定位置,f搞定以后处理 d 时,我们就可以把 f 当做锚点
-
因此我们需要更改逻辑
js// 生成最长子序列 const increasingNewIndexSequence = getSequence(newIndexToOldIndexMap) // 创建一个j 作为最长递增子序列的指针 let j = toBePatched - 1 // ✅ for(let i = toBePatched - 1; i >= 0; i--){ // ✅ // 当前要处理的节点的下一个就是锚点 const nextIndex = i + s2 const nextChild = c2[nextIndex] const anchor = nextIndex + 1 < l2 ? c2[ nextIndex + 1 ].el : null if(i !== increasingNewIndexSequence[j]) { console.log('移动位置'); hostInsert(nextChild.el, container, anchor) // 根据锚点移动位置 } else { j-- // ✅ } }
-
-
上面已经基本完成了中间乱序的排序,下面是一些优化的点
-
j 在 < 0 时,说明不需要移动的子序列已全部处理完毕,此时符合条件,直接可以进行移动
-
最长递增子序列的算法,可以提前进行判断是否需要,如果不需要,直接给一个空数组更加快捷,如何进行判断呢?
- 中间的乱序数组,由旧元素,映射成为新元素,如果旧元素,到新元素,下标是一个变大的效果,如果一个元素下标小于其他元素,那么它其实就能确认是移动了位置的。不移动的话下标是递增的效果。
- 根据上面这个可以记录哪些元素进行了移动
jslet moved = false // ✅ 记录是否需要进行移动 let maxNewIndexSoFar = 0 // ✅ 如果小于该值就需要移动 if (newIndex === undefined) { hostPatchRemove(prevChild.el) } else { // ✅ 设置锁的时机 if(newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex } else { moved = true } newIndexToOldIndexMap[newIndex - s2] = i + 1 patch(prevChild, c2[newIndex], container, parentComponent, null) patched++ } // ✅ const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [] let j = toBePatched - 1 for(let i = toBePatched - 1; i >= 0; i--){ const nextIndex = i + s2 const nextChild = c2[nextIndex] const anchor = nextIndex + 1 < l2 ? c2[ nextIndex + 1 ].el : null if(moved) { // ✅ if(j < 0 && i !== increasingNewIndexSequence[j]) { // j < 0 说明需要不需要移动的子序列已全部处理完毕 console.log('移动位置'); hostInsert(nextChild.el, container, anchor) } else { j-- } } }
-
-
创建新节点,其实在上面处理移动的过程中,我们已经找到了创建的时机
js
const newIndexToOldIndexMap = new Array(toBePatched) // 初始化中间需要调整位置的数组下标,值都统一设置为0
for(let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
if (newIndex === undefined) { // 新节点里面没有,就把老节点删除
hostPatchRemove(prevChild.el)
} else { // 新节点里面有就进行对比渲染
if(newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
newIndexToOldIndexMap[newIndex - s2] = i + 1 // 初始化数组的下标
patch(prevChild, c2[newIndex], container, parentComponent, null)
patched++
}
// 生成最长子序列
const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []
// 创建一个j 作为最长递增子序列的指针
let j = toBePatched - 1
for(let i = toBePatched - 1; i >= 0; i--){
// 当前要处理的节点的下一个就是锚点
const nextIndex = i + s2
const nextChild = c2[nextIndex]
const anchor = nextIndex + 1 < l2 ? c2[ nextIndex + 1 ].el : null
if (newIndexToOldIndexMap[i]===0) { // ✅
patch(null, nextChild, container, parentComponent, anchor)
}else if(moved) {
if(j < 0 ||S i !== increasingNewIndexSequence[j]) { // j < 0 说明需要不需要移动的子序列已全部处理完毕
console.log('移动位置');
hostInsert(nextChild.el, container, anchor)
} else {
j--
}
}
}