
本文详解如何在 JavaScript 中实现带 ID 语义的深度合并:针对 cards 数组中同 iid 的卡片,精准合并其 sub_arr 和 support 等子数组——按 id 字段去重、覆盖而非简单拼接,避免重复项,满足真实业务中“局部更新+保持结构”的核心需求。
本文详解如何在 JavaScript 中实现**带 ID 语义的深度合并**:针对 `cards` 数组中同 `iid` 的卡片,精准合并其 `sub_arr` 和 `support` 等子数组——按 `id` 字段去重、覆盖而非简单拼接,避免重复项,满足真实业务中“局部更新+保持结构”的核心需求。
在实际前端开发中(如卡片式数据管理、实时同步接口响应),我们常遇到这样的场景:初始数据结构复杂嵌套,后续仅收到部分字段的增量更新(如某张卡片的某个子项 event 变更、某位支持者 name 修改),要求按唯一标识(如 iid、id)精准定位并更新,同时保留未变更字段,且子数组需基于 id 合并(相同 id 覆盖,不同 id 追加)。此时,通用的 Object.assign()、展开运算符或 _.merge() 均无法满足——它们要么浅层覆盖,要么对数组做整体替换,导致 sub_arr 或 support 出现重复或丢失。
✅ 正确解法:语义化深度合并(ID-aware Deep Merge)
我们需要一个定制化合并函数,它具备以下能力:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 递归遍历对象,识别数组与普通对象;
- 对顶层
cards数组:按iid匹配卡片,存在则合并,不存在则追加; - 对子数组(如
sub_arr、support):按id字段去重合并(同id项用新值覆盖,新id项追加); - 对其余普通属性:直接覆盖(符合“更新”语义)。
以下是生产可用的实现:
/**
* 按 ID 合并两个对象(专为 cards 场景优化)
* @param {Object} target - 原始数据(将被修改)
* @param {Object} source - 更新数据
* @returns {Object} 合并后的 target(原地修改,也可 clone 后传入)
*/
function idAwareDeepMerge(target, source) {
// 遍历 source 所有键
for (const key in source) {
if (!source.hasOwnProperty(key)) continue;
const targetVal = target[key];
const sourceVal = source[key];
if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
// 特殊处理:cards 数组 → 按 iid 合并
if (key === 'cards') {
mergeCardsArray(targetVal, sourceVal);
}
// 子数组(如 sub_arr, support)→ 按 id 合并
else if (targetVal.length > 0 && typeof targetVal[0] === 'object' && 'id' in targetVal[0]) {
mergeIdArray(targetVal, sourceVal);
} else {
// 普通数组:直接覆盖(或可选 concat)
target[key] = [...sourceVal];
}
}
// 普通对象:递归合并
else if (targetVal && sourceVal && typeof targetVal === 'object' && typeof sourceVal === 'object') {
idAwareDeepMerge(targetVal, sourceVal);
}
// 基本类型或 null/undefined:直接覆盖
else {
target[key] = sourceVal;
}
}
return target;
}
/**
* 合并 cards 数组:按 iid 查找并更新,无则追加
*/
function mergeCardsArray(targetCards, sourceCards) {
sourceCards.forEach(sourceCard => {
const existingIndex = targetCards.findIndex(card => card.iid === sourceCard.iid);
if (existingIndex !== -1) {
// 存在:递归合并该卡片对象(含其 sub_arr/support)
idAwareDeepMerge(targetCards[existingIndex], sourceCard);
} else {
// 不存在:深拷贝后追加(避免引用污染)
targetCards.push(JSON.parse(JSON.stringify(sourceCard)));
}
});
}
/**
* 合并 id 数组(如 sub_arr, support):按 id 去重覆盖
*/
function mergeIdArray(targetArr, sourceArr) {
const idMap = new Map();
// 先存 target 所有项(以 id 为 key)
targetArr.forEach(item => {
if (item.id !== undefined) idMap.set(item.id, { ...item });
});
// 再用 source 覆盖或新增
sourceArr.forEach(item => {
if (item.id !== undefined) {
idMap.set(item.id, { ...item }); // 覆盖同 id 项
}
});
// 转回数组并赋值
targetArr.length = 0; // 清空原数组
idMap.forEach(item => targetArr.push(item));
}
✅ 使用示例
const initialData = { /* 如题中 object1 */ };
const updateData = { /* 如题中 object2 */ };
// 安全合并(推荐先深拷贝 target,避免污染原始数据)
const merged = idAwareDeepMerge(
JSON.parse(JSON.stringify(initialData)),
updateData
);
console.log(merged.cards[0].sub_arr);
// 输出:sub_arr 中 id="94184" 的 event 已更新为 "ghd",其余项保留,无重复
console.log(merged.cards[0].support[0].name); // "dson"(已覆盖)
⚠️ 注意事项与最佳实践
-
不要直接修改原始数据:上述函数默认原地修改。生产环境建议先
structuredClone()(现代浏览器)或JSON.parse(JSON.stringify())深拷贝后再合并,确保不可变性。 -
id字段必须存在且唯一:mergeIdArray依赖item.id作为合并键,若某些项无id,将被忽略;确保业务数据约定一致。 -
性能考量:对超大数组(如
support数百项),Map方案仍高效(O(n+m))。若需极致性能,可预构建索引。 -
扩展性:如需支持其他唯一键(如
_id,uuid),可将id抽为参数;如需自定义合并策略(如数组拼接而非覆盖),可注入mergeFn回调。 -
替代方案参考:
-
structuredClone()+ 手动遍历(ES2022+,支持Date/Map/Set,但不解决 ID 合并逻辑); - Lodash 的
_.mergeWith()配合自定义customizer(需手动处理数组 ID 逻辑); - 不推荐
_.unionBy()单独用于子数组——它仅去重,不支持深层属性覆盖。
-
✅ 总结
真正的“深度合并”不是递归拷贝,而是理解数据语义。当业务要求“按 ID 更新子项”,就必须跳出通用工具的局限,编写符合领域逻辑的合并器。本文提供的 idAwareDeepMerge 函数,兼顾健壮性、可读性与可维护性,可直接集成至数据层(如 Redux Toolkit 的 createEntityAdapter 辅助逻辑,或 Vue/Pinia 的 store mutation),成为你处理嵌套更新场景的可靠基石。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










