functionquickDiff(oldChildren, newChildren) {
// 步骤 1:去除相同前缀let j = 0;
let oldVNode = oldChildren[j];
let newVNode = newChildren[j];
while (oldVNode && newVNode && oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode);
j++;
oldVNode = oldChildren[j];
newVNode = newChildren[j];
}
// 步骤 2:去除相同后缀let oldEnd = oldChildren.length - 1;
let newEnd = newChildren.length - 1;
oldVNode = oldChildren[oldEnd];
newVNode = newChildren[newEnd];
while (oldVNode && newVNode && oldVNode.key === newVNode.key) {
patch(oldVNode, newVNode);
oldEnd--;
newEnd--;
oldVNode = oldChildren[oldEnd];
newVNode = newChildren[newEnd];
}
// 步骤 3:处理中间部分if (j > oldEnd && j <= newEnd) {
// 只有新增for (let i = j; i <= newEnd; i++) {
createNode(newChildren[i]);
}
} elseif (j > newEnd) {
// 只有删除for (let i = j; i <= oldEnd; i++) {
removeNode(oldChildren[i]);
}
} else {
// 有移动和更新const count = newEnd - j + 1;
const source = newArray(count).fill(-1);
const oldStart = j;
const newStart = j;
let moved = false;
let pos = 0;
// 构建 key 到索引的映射const keyIndex = {};
for (let i = newStart; i <= newEnd; i++) {
keyIndex[newChildren[i].key] = i;
}
// 填充 source 数组for (let i = oldStart; i <= oldEnd; i++) {
const oldVNode = oldChildren[i];
const k = keyIndex[oldVNode.key];
if (k !== undefined) {
newVNode = newChildren[k];
patch(oldVNode, newVNode);
source[k - newStart] = i;
if (k < pos) {
moved = true;
} else {
pos = k;
}
} else {
removeNode(oldVNode);
}
}
if (moved) {
// 计算最长递增子序列const seq = getSequence(source);
let s = seq.length - 1;
let i = count - 1;
for (i; i >= 0; i--) {
if (source[i] === -1) {
// 新节点,需要插入createNode(newChildren[i + newStart]);
} elseif (i !== seq[s]) {
// 需要移动moveNode(oldChildren[source[i]], newChildren[i + newStart]);
} else {
// 不需要移动
s--;
}
}
}
}
// 最长递增子序列算法(简化版)functiongetSequence(arr) {
const result = [0];
const p = arr.slice();
let len = arr.length;
let i, j, u, v, c;
for (i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== -1) {
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]] < arrI) {
u = c + 1;
} else {
v = c;
}
}
if (arrI < arr[result[u]]) {
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;
}
}
优缺点
优点:
✅ 处理前缀后缀相同的场景效率极高
✅ 通过 LIS 算法最小化移动操作
✅ 整体性能优于双端 Diff
缺点:
❌ 实现复杂度较高
❌ LIS 算法本身有一定开销
简单 Diff 算法
核心思想
简单 Diff 就像'暴力比较',逐个比较每个节点,找到就更新,找不到就删除或新增。
算法步骤
遍历新列表
在旧列表中查找相同 key 的节点
找到就更新,找不到就新增
最后删除旧列表中多余的节点
具体示例
示例 1:简单场景
旧列表:[A, B, C] 新列表:[C, A, B]
步骤 1:处理新列表的第一个节点 C
在旧列表中查找 C → 找到(索引 2)
更新节点 C
移动 C 到位置 0
步骤 2:处理新列表的第二个节点 A
在旧列表中查找 A → 找到(索引 0)
更新节点 A
移动 A 到位置 1
步骤 3:处理新列表的第三个节点 B
在旧列表中查找 B → 找到(索引 1)
更新节点 B
移动 B 到位置 2
结果:移动了 3 个节点(效率较低)
代码实现(简化版)
functionsimpleDiff(oldChildren, newChildren) {
// 构建旧列表的 key 到索引映射const oldKeyToIndex = {};
for (let i = 0; i < oldChildren.length; i++) {
oldKeyToIndex[oldChildren[i].key] = i;
}
let lastIndex = 0;
// 遍历新列表for (let i = 0; i < newChildren.length; i++) {
const newVNode = newChildren[i];
const j = oldKeyToIndex[newVNode.key];
if (j !== undefined) {
// 找到节点,更新patch(oldChildren[j], newVNode);
if (j < lastIndex) {
// 需要移动moveNode(oldChildren[j], i);
} else {
lastIndex = j;
}
} else {
// 新节点,需要创建createNode(newVNode);
}
}
// 删除旧列表中多余的节点for (let i = 0; i < oldChildren.length; i++) {
const oldVNode = oldChildren[i];
const has = newChildren.some(vnode => vnode.key === oldVNode.key);
if (!has) {
removeNode(oldVNode);
}
}
}