1. Possibilities (common):
1.
旧的:a b c
新的:a b c d
2.
旧的: a b c
新的:d a b c
3.
旧的:a b c d
新的:a b c
4.
旧的:d a b c
新的: a b c
5.
旧的:a b c d e i f g
新的:a b e c d h f g
Corresponding real virtual nodes (for ease of understanding, letters are used in the text):
// vnode对象
const a = {
type: 'div', // 标签
props: {style: {color: 'red'}}, // 属性
children: [], // 子元素
key: 'key1', // key
el: '<div style="color: 'red'"></div>', // 真实dom节点
...
}
Second, find the rules
Remove the same part before and after
// c1表示旧的子节点,c2表示新的子节点
const patchKeyedChildren = (c1, c2) => {
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
// 从前面比
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
// 标签和key是否相同
// if (n1.type === n2.type && n1.key === n2.key)
if (n1 === n2) {
// 继续对比其属性和子节点
} else {
break
}
i++
}
// 从后面比
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
// 标签和key是否相同
// if (n1.type === n2.type && n1.key === n2.key)
if (n1 === n2) {
// 继续对比其属性和子节点
} else {
break
}
e1--
e2--
}
console.log(i, e1, e2)
}
// 调用示例
patchKeyedChildren(['a', 'b', 'c', 'd'], ['a', 'b', 'c'])
With this function you can get:
1.
旧的:a b c
新的:a b c d
i = 3 e1 = 2 e2 = 3
2.
旧的: a b c
新的:d a b c
i = 0 e1 = -1 e2 = 0
3.
旧的:a b c d
新的:a b c
i = 3 e1 = 3 e2 = 2
4.
旧的:d a b c
新的: a b c
i = 0 e1 = 0 e2 = -1
5.
旧的:a b c d e i f g
新的:a b e c d h f g
i = 2 e1 = 5 e2 = 5
Extension:
旧的:a b c
新的:a b c d e f
i = 3 e1 = 2 e2 = 5
旧的:a b c
新的:a b c
i = 3 e1 = 2 e2 = 2
旧的:e d a b c
新的: a b c
i = 0 e1 = 1 e2 = -1
旧的:c d e
新的:e c d h
i = 0 e1 = 2 e2 = 3
From the above results we can find the rule:
- When i is greater than e1, just add new child nodes
- When i is greater than e2, just delete the old child
// 当i大于e1时
if (i > e1) {
if (i <= e2) {
while (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < c2.length ? c2[nextPos].el : null
// 添加新的子节点c2[i]在anchor的前面
// todo
i++
}
}
}
// 当i大于e2时
else if (i > e2) {
if (i <= e1) {
while (i <= e1) {
// 删除旧的子节点c1[i]
// todo
i++
}
}
}
- Other, special treatment
// 其它
let s1 = i
let s2 = i
// 以新的子节点作为参照物
const keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
// 节点的key做为唯一值
// keyToNewIndexMap.set(c2[i].key, i)
keyToNewIndexMap.set(c2[i], i)
}
// 新的总个数
const toBePatched = e2 - s2 + 1
// 记录新子节点在旧子节点中的索引
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
// 循环老的子节点
for (let i = s1; i <= e1; i++) {
const oldChild = c1[i]
// let newIndex = keyToNewIndexMap.get(oldChild.key)
let newIndex = keyToNewIndexMap.get(oldChild)
// 在新子节点中不存在
if (newIndex === undefined) {
// 删除oldChild
// todo
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1 // 永远不会等于0, 这样0就可以表示需要创建了
// 继续对比其属性和子节点
// todo
}
}
console.log(newIndexToOldIndexMap)
// 需要移动位置
for (let i = toBePatched - 1; i >= 0; i--) {
let index = i + s2
let current = c2[index]
let anchor = index + 1 < c2.length ? c2[index + 1].el : null
if (newIndexToOldIndexMap[i] === 0) {
// 在anchor前面插入新的节点current
// todo
} else {
// 在anchor前面插入对应旧节点.el,current.el元素等于对应的旧节点.el(在其它代码中赋值了)
// todo
}
}
The 1st and 2nd types are relatively simple, and we won't go into too much explanation. Let's take a look at the 3rd type. Take the following as an example
序号: 0 1 2 3 4 5 6 7
旧的:(a b) c d e i (f g)
新的:(a b) e c d h (f g)
- The front ab and the back fg are the same and will be removed, leaving only the out-of-order part in the middle
- Taking the new node as a reference, loop the old node, and remove the new node from the old node, such as i
- Mark the node that does not exist in the old one, if not, it will be 0, indicating that it needs to be created; if there is, the serial number +1, indicating that it can be reused
标记: 4+1 2+1 3+1 0
新的:(...) e c d h (...)
- Cycle from back to front, h is 0, create it, put it in front of its next f; if d is not 0, reuse the d in the old one, put it in front of h; if c is not 0, reuse the old one c, put it in front of d; if e is not 0, reuse the e in the old one, put it in front of c
So far, the replacement of the old and new elements has been completed, but have you found a problem, the four elements of ecdh have moved once, we can see that if only e is moved and h is created, c and d remain unchanged, the efficiency will be higher
3. Algorithm optimization
1.
序号: 0 1 2 3 4 5 6 7
旧的:(a b) c d e i (f g)
新的:(a b) e c d h (f g)
对应的标记是[5, 3, 4, 0]
2.
序号:0 1 2 3 4 5
旧的:c d e i f g
新的:e c d f g j
对应的标记是[3, 1, 2, 5, 6, 0]
As can be seen from the above two examples:
The optimal solution for the 1st is to find cd, just move e to create h
The optimal solution for the 2nd is to find the cdfg, just move e to create j
process:
- Find longest increasing subsequence from markers
- Find the corresponding index value by the longest increasing subsequence
- Find the corresponding value by index value
Note that 0 means direct creation and does not participate in calculation
example:
- The longest increasing subsequence of [3, 1, 2, 5, 6, 0] is [1, 2, 5, 6],
- The corresponding index is [1, 2, 3, 4],
- Then we traverse ecdfgj, the mark is 0, such as j, create it directly; the cdfg index is equal to 1 2 3 4, respectively, remain unchanged; e is equal to 0, move
// 需要移动位置
// 找出最长的递增子序列对应的索引值,如:[5, 3, 4, 0] -> [1, 2]
let increment = getSequence(newIndexToOldIndexMap)
console.log(increment)
let j = increment.length - 1
for (let i = toBePatched - 1; i >= 0; i--) {
let index = i + s2
let current = c2[index]
let anchor = index + 1 < c2.length ? c2[index + 1].el : null
if (newIndexToOldIndexMap[i] === 0) {
// 在anchor前面插入新的节点current
// todo
} else {
if (i !== increment[j]) {
// 在anchor前面插入对应旧节点.el,current.el元素等于对应的旧节点.el(在其它代码中赋值了)
// todo
} else { // 不变
j--
}
}
}
longest increasing subsequence
https://segmentfault.com/a/1190000042199390
full code
// 最长的递增子序列
function getSequence(arr) {
const len = arr.length
const min_arr = [0] // 存储最小的索引,以索引0为基准
const prev_arr = arr.slice() // 储存前面的索引,slice为浅复制一个新的数组
let last_index
let start
let end
let middle
for (let i = 0; i < len; i++) {
let arrI = arr[i]
if (arrI !== 0) { // vue中为0,表示直接创建
// 1. 如果当前n比min_arr最后一项大
last_index = min_arr[min_arr.length - 1]
if (arr[last_index] < arrI) {
min_arr.push(i)
prev_arr[i] = last_index // 前面的索引
continue
}
// 2. 如果当前n比min_arr最后一项小(二分类查找)
start = 0
end = min_arr.length - 1
while(start < end) {
middle = (start + end) >> 1 // 相当于Math.floor((start + end)/2)
if (arr[min_arr[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
if (arr[min_arr[end]] > arrI) {
min_arr[end] = i
if (end > 0) {
prev_arr[i] = min_arr[end - 1] // 前面的索引
}
}
}
}
// 从最后一项往前查找
let result = []
let i = min_arr.length
let last = min_arr[i - 1]
while(i-- > 0) {
result[i] = last
last = prev_arr[last]
}
return result
}
// c1表示旧的子节点,c2表示新的子节点
const patchKeyedChildren = (c1, c2) => {
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
// 从前面比
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
// 标签和key是否相同
// if (n1.type === n2.type && n1.key === n2.key)
if (n1 === n2) {
// 继续对比其属性和子节点
} else {
break
}
i++
}
// 从后面比
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
// 标签和key是否相同
// if (n1.type === n2.type && n1.key === n2.key)
if (n1 === n2) {
// 继续对比其属性和子节点
} else {
break
}
e1--
e2--
}
console.log(i, e1, e2)
// 当i大于e1时
if (i > e1) {
if (i <= e2) {
while (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < c2.length ? c2[nextPos].el : null
// 添加子节点c2[i]在anchor的前面
// todo
i++
}
}
}
// 当i大于e2时
else if (i > e2) {
if (i <= e1) {
while (i <= e1) {
// 删除子节点c1[i]
// todo
i++
}
}
}
// 其它
else {
let s1 = i
let s2 = i
// 以新的子节点作为参照物
const keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
// 节点的key做为唯一值
// keyToNewIndexMap.set(c2[i].key, i)
keyToNewIndexMap.set(c2[i], i)
}
// 新的总个数
const toBePatched = e2 - s2 + 1
// 记录新子节点在旧子节点中的索引
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
// 循环老的子节点
for (let i = s1; i <= e1; i++) {
const oldChild = c1[i]
// let newIndex = keyToNewIndexMap.get(oldChild.key)
let newIndex = keyToNewIndexMap.get(oldChild)
// 在新子节点中不存在
if (newIndex === undefined) {
// 删除oldChild
// todo
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1 // 永远不会等于0, 这样0就可以表示需要创建了
// 继续对比其属性和子节点
// todo
}
}
console.log(newIndexToOldIndexMap)
// 需要移动位置
// 找出最长的递增子序列对应的索引值,如:[5, 3, 4, 0] -> [1, 2]
let increment = getSequence(newIndexToOldIndexMap)
console.log(increment)
let j = increment.length - 1
for (let i = toBePatched - 1; i >= 0; i--) {
let index = i + s2
let current = c2[index]
let anchor = index + 1 < c2.length ? c2[index + 1].el : null
if (newIndexToOldIndexMap[i] === 0) {
// 在anchor前面插入新的节点current
// todo
} else {
if (i !== increment[j]) {
// 在anchor前面插入对应旧节点.el,current.el元素等于对应的旧节点.el(在其它代码中赋值了)
// todo
} else { // 不变
j--
}
}
}
}
}
// 调用示例
patchKeyedChildren(['c', 'd', 'e', 'i', 'f', 'g'], ['e', 'c', 'd', 'f', 'g', 'j'])
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。