在Vue和React中都有diff的存在,区别是Vue把diff控制在组件内,而React是通过Fiber优化。下面我们把diff最简化的讲出来。
diff的作用是对比新旧VNode,然后执行patch函数更新dom。在这个过程中key很重要,为了方便大家理解我对VNode做了简化。使用{key: xxx}代表VNode。
diff的目标:新建dom会消耗性能,所以oldVNode中存在的dom,尽量通过移动达到目的。不存在的再进行新建,多余的删除。
搞起吧
1、新建newVNode、oldVNode
const oldVNode = [
{ key: 1 }, { key: 2 }, { key: 3 }, { key: 4 }, { key: 5 },
{ key: 6 }, { key: 7 }, { key: 8 }, { key: 9 }, { key: 10 }
]
const newVNode = [
{ key: 1 }, { key: 10 }, { key: 9 }, { key: 5 },
{ key: 22 }, { key: 8 }, { key: 7 }, { key: 2 },
{ key: 17 }
]
2、新建map,储存oldVNode中所有的key
const map = new Map();
for (let i of oldVNode) {
map.set(i.key, i)
}
3、使用‘双指针’方法进行对比
因为是用数组模拟,所以使用splice模拟dom移动、新建、删除。
let newIndex = 0;
let oldIndex = 0;
// ‘双指针’都不能超过界限
while (newIndex < newVNode.length && oldIndex < oldVNode.length) {
// 当oldVNode中不存在newVNode[newIndex]时
if (!map.has(newVNode[newIndex])) {
// 在oldVNode中新建newVNode[newIndex]
oldVNode.splice(newIndex, 0, newVNode[newIndex])
// 更新指针
newIndex++;
oldIndex++;
} else { // 当newVNode[newIndex]存在于oldVNode中时
// newVNode[newIndex]和oldVNode[oldIndex]不相等
if (newVNode[newIndex] !== oldVNode[oldIndex]) {
// 更新oldIndex
oldIndex++;
} else { // newVNode[newIndex]和oldVNode[oldIndex]不相等
// 使用下面代码,模拟dom移动
const dom = oldVNode.splice(oldVNode, 1);
oldVNode.splice(newIndex, 0, ...dom);
// 更新指针
newIndex++;
oldIndex = newIndex;
}
}
}
4、因为做了‘双指针’界限,所以还要处理剩余的dom
// oldVNode有剩余,则全部删除
if (oldIndex < oldVNode.length) {
oldVNode.splice(oldIndex)
}
// newIndex有剩余,则全部新建
if (newIndex < newVNode.length) {
oldVNode.push(...newVNode.slice(newIndex))
}
全部代码
const oldVNode = [
{ key: 1 },
{ key: 2 },
{ key: 3 },
{ key: 4 },
{ key: 5 },
{ key: 6 },
{ key: 7 },
{ key: 8 },
{ key: 9 },
{ key: 10 }
]
const newVNode = [
{ key: 1 },
{ key: 10 },
{ key: 9 },
{ key: 5 },
{ key: 22 },
{ key: 8 },
{ key: 7 },
{ key: 2 },
{ key: 17 },
]
const map = new Map();
for (let i of oldVNode) {
map.set(i.key, i)
}
let newIndex = 0;
let oldIndex = 0;
while (newIndex < newVNode.length && oldIndex < oldVNode.length) {
if (!map.has(newVNode[newIndex])) {
oldVNode.splice(newIndex, 0, newVNode[newIndex])
newIndex++;
oldIndex++;
} else {
if (newVNode[newIndex] !== oldVNode[oldIndex]) {
oldIndex++;
} else {
const dom = oldVNode.splice(oldVNode, 1);
oldVNode.splice(newIndex, 0, ...dom);
newIndex++;
oldIndex = newIndex;
}
}
}
if (oldIndex < oldVNode.length) {
oldVNode.splice(oldIndex)
}
if (newIndex < newVNode.length) {
oldVNode.push(...newVNode.slice(newIndex))
}
console.log(oldVNode)
console.log(newVNode)