入口 patchKeyedChildren
就是第九节中 当新旧子节点都是数组时进行diff算法
// 有key的情况下 比较两个子节点的差异
const patchKeyedChildren = (c1, c2, el) => {
let i = 0;
// 拿到子集的长度
let e1 = c1.length - 1;
let e2 = c2.length - 1;
//...
}
const patchChildren = (n1, n2, el) => {
const c1 = n1 && n1.children
const c2 = n2 && n2.children
const prevShapeFlag = n1.shapeFlag;
const shapeFlag = n2.shapeFlag;
if(shapeFlag & ShapeFlags.TEXT_CHILDREN){ // 1、新节点是 文本
//...
}else {
if(prevShapeFlag & ShapeFlags.ARRAY_CHILDREN){ // 2、新的 是数组
if(shapeFlag & ShapeFlags.ARRAY_CHILDREN){ // 数组 数组
// diff算法
patchKeyedChildren(c1, c2, el); // 全量更新 全量比对
}else{
//...
}
}else{ // 3、新的既不是文本 也不是 数组,就剩下一种情况为空
// ...
}
}
}
diff算法的整体流程
- 从头比对一遍 (动索引)
- 从尾比对一遍 (动子集的长度)
- 同序列加挂载
i>e1 && i<e2
- 同序列加卸载
i>e2 && i<=e1
- 乱序比对
- 5.1、找到 新的vnode需要比对区域
- 5.2、遍历老的vnode的需要比对区域
- 5.3、移动位置 倒序插入
实现
有key的情况下 比较两个子节点的差异
1. sync from start: i <= e1 && i <= e2
有任何一方停止循环则立即跳出 (主要是找i)
移动索引下标i
找到从起始位置 的最大的复用元素索引(i)(0到i就是 从起始位置开始到有变化位置不用动元素)
// 1. sync from start: i <= e1 && i <= e2
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) { // 有任何一方停止循环则立即跳出
const n1 = c1[i];
const n2 = c2[i];
if (isSameVNodeType(n1, n2)) { // 是同一个
patch(n1, n2, el) // 这样做就是递归比较两个节点的属性和子节点
} else {
break;
}
// 移动的是下标指针 i
i++;
}
// console.log(i, e1, e2) // 2 2 3 尽可能减少比较的内容
2. sync from end: i <= e1 && i <= e2
主要是找(e1,e2)
移动的是新老子节点的长度
找到从结尾位置 开始的 最大的复用元素索引(e1,e2)(从结尾位置开始到有变化位置,就是不用动的元素)
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, el);
} else {
break;
}
// 移动的是 两个目标的长度
e1--;
e2--;
}
console.log(i, e1, e2) // 0 0 1 ,i一直是经过从头比对的索引 e1比较短走到了0 e2差一位为1
查找示例:
下边的 3、4步有一方比较完毕了 要么删除 要么添加
3. common sequence + mount 同序列加挂载 i>e1 && i<=e2
说明有新增
上边两种循环 都是
i 要比e1大说明有新增的部分
i停留的值 和 e2之间的 为新增的
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) { // 说明有新增
if (i <= e2) { // 表示有新增的部分
// 先根据e2(因为e2是新的vnode) 取他的下一个元素索引extPos 与c2的长度比较看是否超出 超出的话直接push 不超出的话要插入到c2[nextPos].el 之前
const nextPos = e2 + 1;
// extPos >= c2.length 说明直接push到容器后边即可
// extPos < c2.length 说明需要插入到指定元素前边
const anchor = nextPos < c2.length ? c2[nextPos].el : null;
while (i <= e2) {
// 创建新节点扔到容器中 anchor是insert用的 层层传递;
// anchor为null 说明直接push到尾部即可
patch(null, c2[i], el, anchor);
i++;
}
}
}
4. common sequence + unmount 同序列加卸载 i>e2 && i<=e1
i比e2大说明有要卸载的
i到e1之间的就是要卸载的
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1 删除第2个
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1 删除第0个
else if (i > e2) {
while (i <= e1) {
unmount(c1[i]) // 删掉老的
i++
}
}
完整版
// 有key的情况下 比较两个子节点的差异
const patchKeyedChildren = (c1, c2, el) => {
let i = 0;
// 拿到子集的长度
let e1 = c1.length - 1;
let e2 = c2.length - 1;
// 1. sync from start
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) { // 有任何一方停止循环则立即跳出
const n1 = c1[i];
const n2 = c2[i];
if (isSameVNodeType(n1, n2)) { // 是同一个
patch(n1, n2, el) // 这样做就是递归比较两个节点的属性和子节点
} else {
break;
}
// 移动的是下标指针 i
i++;
}
// console.log(i, e1, e2) // 2 2 3 尽可能减少比较的内容
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, el);
} else {
break;
}
// 移动的是 两个目标的长度
e1--;
e2--;
}
console.log(i, e1, e2) // 0 0 1 ,i一直是经过从头比对的索引 e1比较短走到了0 e2差一位为1
// 下边的 3、4步有一方比较完毕了 要么删除 要么添加
// 3. common sequence + mount 同序列加挂载 i>e1 && i<e2
// 上边两种循环 都是
// i 要比e1大说明有新增的部分
// i停留的值 和 e2之间的 为新增的
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) { // 说明有新增
if (i <= e2) { // 表示有新增的部分
// 先根据e2 取他的下一个元素 看是否存在 存在的话往前插 不存在的话往后插
const nextPos = e2 + 1;
const anchor = nextPos < c2.length ? c2[nextPos].el : null;
while (i <= e2) {
patch(null, c2[i], el, anchor); // 创建新节点扔到容器中 anchor是insert用的 层层传递
i++;
}
}
}
// 4. common sequence + unmount 同序列加卸载 i>e2 && i<=e1
// i比e2大说明有要卸载的
// i到e1之间的就是要卸载的
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i]) // 删掉老的
i++
}
}
// 乱序比对
}
下一章我们来实现乱序比对