JavaScript如何深度合并两个对象并保留属性层级结构?
问题原因
Object.assign 本身只做第一层属性的浅合并,遇到值是对象、数组这类引用类型的同名属性时,会直接拿后传入对象的属性值整体覆盖前序对象的对应值,不会递归往里合并嵌套的属性,这就是示例里issues字段被整个替换、global属性丢失的原因。
通用实现方案
要实现保留嵌套结构的深度合并,实现思路很直接:递归遍历所有待合并对象的属性,只要同名属性的两侧值都是纯对象,就进入下一层继续合并,而不是直接覆盖整个属性值,适配任意未知属性结构的场景。
无依赖手写实现
以下实现和Object.assign的参数逻辑保持一致,默认会修改第一个传入的目标对象,兼容所有常规JS数据类型场景:
function deepMerge(target, ...sources) { // 目标值不是有效对象时初始化为空对象 if (typeof target !== 'object' || target === null) { target = {}; } for (const source of sources) { // 跳过null、非对象类型的无效源 if (typeof source !== 'object' || source === null) continue; for (const key of Object.keys(source)) { const targetValue = target[key]; const sourceValue = source[key]; // 两侧值均为非数组的纯对象时,递归合并内部属性 if ( typeof targetValue === 'object' && targetValue !== null && typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(targetValue) && !Array.isArray(sourceValue) ) { target[key] = deepMerge(targetValue, sourceValue); } else { // 基础类型、数组、函数等类型直接覆盖,如有数组合并需求可在此扩展逻辑 target[key] = sourceValue; } } } return target; }
使用示例
const object1 = { issues: { global: 'a great string!' } }; const object2 = { issues: { cooldown: 'oh well, another message!' } }; deepMerge(object1, object2); console.log(object1); // 输出结果符合预期:{ issues: { global: 'a great string!', cooldown: 'oh well, another message!' } }
使用注意
- 如果不希望修改原对象,调用时将第一个参数传入空对象即可:
const mergedResult = deepMerge({}, object1, object2),原对象不会被改动。 - 上述实现默认遇到数组时直接用源数组覆盖目标值,如果业务需要数组合并(比如拼接、按唯一ID去重合并数组项),可以在对应判断分支中自行扩展逻辑。
- 不要用
JSON.parse(JSON.stringify())配合对象展开运算符实现合并,这种方案会丢失函数、undefined、Symbol类型的属性,还会错误转换日期、正则等特殊对象,仅适合纯JSON数据场景,通用性极差。
工具库方案
如果项目中已经引入了Lodash等通用工具库,可以直接使用库内置的_.merge方法,该方法已经处理了所有边界场景,逻辑和上述手写实现一致,可直接调用。
内容的提问来源于stack exchange,提问作者Sotot
相关产品推荐
相关产品推荐

