当数据有key时
从前向后比
声明一个i,i从0开始循环,e1为旧值长度减1,e2为新值长度减1,当i大于e1或者大于e2或者有一项不相等时就结束循环。
那么如果长度不等,那么剩余的长度就是待操作的节点。
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];
if (isSameVNode(n1, n2)) {
patch(n1, n2, el);
} else {
break;
}
i++;
}
从后向前比
e1为旧值长度减1,e2为新值长度减1,当i大于e1或者大于e2或者有一项不相等时就结束循环。
那么如果长度不等,那么剩余的长度就是待操作的节点。
// 从后向前比
let i = 0;
let e1 = c1.length - 1;
let e2 = c2.length - 1;
while(i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNode(n1, n2)) {
patch(n1, n2, el);
} else {
break;
}
e1--;
e2--;
}
新值长度大于旧值长度(挂载)
不管是从前向后还是从后向前,如果i比e1大的话说明有新增项目,i和e2之间的就是新增的内容
// 如果i比e1大的话说明有新增项目,i和e2之间的就是新增的内容
if (i> e1) {
if (i <= e2) {
while (i <= e2) {
const nextNode = e2 + 1;
// 根据是否存在下一个node,判断是向前插入还是向后插入元素
const anchor = nextNode < c2.length ? c2[nextNode].el : null;
patch(null, c2[i], el, anchor); // 创建新节点到容器中
i++;
}
}
}
新值长度小于旧值长度(卸载)
当i大于e2,并且小于等于e1时,就应该执行卸载逻辑了,i到e1之间的就是卸载的
if (i > e2) {
// 当i大于e2,并且小于等于e1时,就应该执行卸载逻辑了
if (i <= e1) {
while (i <= e1) {
unmount(c1[i]);
i++;
}
}
}
乱序对比
掐头去尾,找出需要操作的数据,并将新节点索引保存,后面可以复用
// 乱序对比
let s1 = i;
let s2 = i;
// 新节点索引映射
const keyToNewIndexMap = new Map();
for (let i = s2; i <= e2; i++) {
keyToNewIndexMap.set(c2[i].key, i);
}
// 新的个数
const toBePatched = e2 - s2 + 1;
// 记录新增的是否进行过处理
const newIndexCorOldMap = new Array(toBePatched).fill(0);
// 循环旧节点,查找是否在映射中存在
// 如果有,对比差异
// 如果没有执行卸载
for (let i = s1; i <= e1; i++) {
const oldChild = c1[i];
const newChildIndex = keyToNewIndexMap.get(oldChild.key);
if (!newChildIndex) {
unmount(oldChild);
} else {
newIndexCorOldMap[newChildIndex - s2] = i + 1;
patch(oldChild, c2[newChildIndex], el);
}
}
// 按顺序插入
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 (newIndexCorOldMap[i] === 0) {
// 新建元素
patch(null, current, el, anchor);
} else {
// 复用
hostInsert(current.el, el, anchor);
}
}
最长递增子序列
找出无需操作的元素,减少dom操作次数,需要找出最长递增子序列个数
比如:[14,2,8,13,6,4,12,15],求出来就是:[2,4,12,15]
贪心算法 > 二分查找 > 索引追溯
function getSequence(arr) {
const len = arr.length;
const res = [0];
let indexOfLast;
let start;
let end;
let middle;
// 前一项索引标记
const p = new Array(arr.length).fill(0);
for (let i = 0; i < arr.length; i++) {
const current = arr[i];
// vue中0是代表新建
if (current !== 0) {
indexOfLast = res[res.length - 1];
// 比较最后一项和当前项的值,如果比最后一项大的话就将当前值放到结果中
if (arr[indexOfLast] < arr[i]) {
res.push(i);
// 记录前一项
p[i] = indexOfLast;
continue;
}
// 二分查找索引
// 在结果中找到比当前值打的,用当前值索引替换
start = 0;
end = res.length - 1;
while (start < end) {
middle = ((start + end) / 2) | 0;
if (arr[res[middle]] < current) {
start = middle + 1;
} else {
end = middle;
}
}
// 找到中间值,进行替换
if (arr[res[end]] > current) {
res[end] = i;
// 记录前一项
p[i] = res[end - 1];
}
}
}
// 通过最后一项向前追溯
let i = res.length;
let last = res[i-1];
while(i-- > 0) {
res[i] = last;
last = p[last];
}
return res;
}
renderer渲染时增加节点比对逻辑
// 比较两个节点的差异
const patchKeyChildren = (c1, c2, el) => {
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];
if (isSameVNode(n1, n2)) {
patch(n1, n2, el);
} else {
break;
}
i++;
}
// 从后向前比
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNode(n1, n2)) {
patch(n1, n2, el);
} else {
break;
}
e1--;
e2--;
}
// 如果i比e1大的话说明有新增项目,i和e2之间的就是新增的内容
if (i > e1) {
if (i <= e2) {
while (i <= e2) {
const nextNode = e2 + 1;
// 根据是否存在下一个node,判断是向前插入还是向后插入元素
const anchor = nextNode < c2.length ? c2[nextNode].el : null;
patch(null, c2[i], el, anchor); // 创建新节点到容器中
i++;
}
}
} else if (i > e2) {
// 当i大于e2,并且小于等于e1时,就应该执行卸载逻辑了
if (i <= e1) {
while (i <= e1) {
unmount(c1[i]);
i++;
}
}
}
// 乱序对比
let s1 = i;
let s2 = i;
// 新节点索引映射
const keyToNewIndexMap = new Map();
for (let i = s2; i <= e2; i++) {
keyToNewIndexMap.set(c2[i].key, i);
}
// 新的个数
const toBePatched = e2 - s2 + 1;
// 记录新增的是否进行过处理
const newIndexCorOldMap = new Array(toBePatched).fill(0);
// 循环旧节点,查找是否在映射中存在
// 如果有,对比差异
// 如果没有执行卸载
for (let i = s1; i <= e1; i++) {
const oldChild = c1[i];
const newChildIndex = keyToNewIndexMap.get(oldChild.key);
if (!newChildIndex) {
unmount(oldChild);
} else {
newIndexCorOldMap[newChildIndex - s2] = i + 1;
patch(oldChild, c2[newChildIndex], el);
}
}
// 获取最长递增序列
let increment = getSequence(newIndexCorOldMap);
// 按顺序插入
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 (newIndexCorOldMap[i] === 0) {
// 新建元素
patch(null, current, el, anchor);
} else {
// 复用
if (i !== increment[j]) {
hostInsert(current.el, el, anchor);
} else {
j--;
}
}
}
};