
本文介绍一种时间复杂度接近 o(n+m) 的算法:通过 map 预建立构造函数到实例的映射,遍历目标数组时优先复用已有对象,仅对缺失类型创建新实例,从而最小化构造开销并原地更新原数组。
本文介绍一种时间复杂度接近 o(n+m) 的算法:通过 map 预建立构造函数到实例的映射,遍历目标数组时优先复用已有对象,仅对缺失类型创建新实例,从而最小化构造开销并原地更新原数组。
在前端状态同步、虚拟 DOM diff、或组件实例复用等场景中,常需将一个现有对象数组(如 arrayB)动态调整为与另一个参考数组(如 arrayA)类型序列完全一致,且要求:
- 保持
arrayA中类的顺序与数量; - 尽可能复用
arrayB中已存在的、类型匹配的对象实例(避免不必要构造); -
原地修改
arrayB(而非返回新数组),以维持引用一致性(例如 Vue/React 中的响应式依赖或生命周期管理)。
核心思路:哈希预索引 + 单次遍历
暴力解法(如对 arrayA 每个元素调用 arrayB.find(item => item.constructor === a.constructor))会导致 O(n×m) 时间复杂度。更优方案是利用 Map 实现 O(1) 平均查找:
-
构建类型索引:遍历
arrayB,以item.constructor为 key,item为 value 存入Map; -
清空并重建:清空
arrayB(arrayB.length = 0),然后按arrayA顺序逐项处理:- 若
Map中存在对应构造函数 → 复用该实例; - 否则 → 调用
new a.constructor()创建新实例;
- 若
-
推入结果:将复用或新建的实例
push到arrayB。
该算法总时间复杂度为 O(n + m)(n = arrayA.length, m = arrayB.length),空间复杂度为 O(m),是理论最优解之一。
示例代码实现
// 辅助基类(仅用于演示构造日志)
class Parent {
constructor() {
console.log(`✅ Constructed ${this.constructor.name}`);
}
}
class Object1 extends Parent {}
class Object2 extends Parent {}
class Object3 extends Parent {}
class Object4 extends Parent {}
class Object5 extends Parent {}
class Object6 extends Parent {}
// 初始化数据
const arrayA = [new Object1(), new Object2(), new Object3()];
const arrayB = [new Object4(), new Object1(), new Object2(), new Object6()];
// ✅ 高效 diff & reuse
const typeMap = new Map();
arrayB.forEach(item => typeMap.set(item.constructor, item)); // O(m)
arrayB.length = 0; // 原地清空
arrayA.forEach(target => {
const existing = typeMap.get(target.constructor);
arrayB.push(existing ?? new target.constructor()); // O(n)
});
console.log(arrayB.map(x => x.constructor.name));
// 输出: ['Object1', 'Object2', 'Object3']
// 控制台仅打印 2 次 "✅ Constructed Object1" 和 "✅ Constructed Object2"(来自 arrayB 初始化),
// 以及 1 次 "✅ Constructed Object3"(本次新建)——验证复用成功。
注意事项与边界处理
-
构造函数唯一性假设:本方案依赖
item.constructor能准确标识类型。若存在继承链过深、动态构造函数或 Symbol 修饰等情况,需改用Object.prototype.toString.call(item)或自定义类型标识符(如item.$$typeKey)替代constructor。 -
重复类型处理:当前逻辑对
arrayB中同一构造函数的多个实例仅保留最后一个(因Map.set()覆盖)。如需支持多实例复用(如arrayA = [Obj1, Obj1]),应改用Map<constructor array>></constructor>并维护实例队列。 -
不可变对象防护:若
arrayB中对象被冻结(Object.freeze())或处于 Proxy 代理下,复用时需确保其行为符合预期。 -
内存安全:
typeMap在操作完成后可手动置为null,避免意外持有arrayB原有对象引用导致内存泄漏。
算法命名参考
该模式属于 "Greedy Type-Based Diffing",与以下经典算法思想相通:
- Longest Common Subsequence (LCS) 的轻量变种:但此处不关注最长子序列,而是强制对齐目标序列;
- React Fiber 的 Reconciliation(协调)策略:核心思想均为“复用已有节点,仅更新/新增必要部分”;
-
Hash-based Patching:常见于增量更新库(如
immer的底层 diff 逻辑),强调通过哈希加速类型匹配。
掌握此模式,可显著提升涉及对象生命周期管理的系统性能,尤其适用于高频更新的可视化应用或状态驱动 UI 框架。










