如何实现JavaScript递归合并对象与数组且保留原属性?
解决方案:递归合并对象与数组的函数
直接上能解决问题的合并函数:
function deepMerge(target, source) { // 源为非对象/数组时,直接返回源(符合更新覆盖逻辑) if (source === null || typeof source !== 'object') { return source; } // 处理数组场景 if (Array.isArray(target)) { if (Array.isArray(source)) { // 遍历原数组,仅合并源数组对应索引存在的元素 return target.map((item, index) => { return source[index] !== undefined ? deepMerge(item, source[index]) : item; }); } // 禁止非数组覆盖数组,避免意外类型转换 throw new Error('Cannot merge non-array into array'); } // 处理对象场景 if (typeof target === 'object' && typeof source === 'object') { const merged = { ...target }; for (const key in source) { if (source.hasOwnProperty(key)) { merged[key] = deepMerge(target[key], source[key]); } } return merged; } // 其他非常规场景,直接返回源 return source; }
关键逻辑说明
数组的精准处理:
- 仅当原目标和更新源都是数组时,才按索引递归合并元素:
- 对象数组:合并对应索引的对象属性,原对象的
connections等属性不会丢失 - 字符串/基本类型数组:仅替换源中存在索引的元素,不会把数组转成索引键对象
- 对象数组:合并对应索引的对象属性,原对象的
- 若尝试用非数组更新数组,直接抛出错误,避免意外类型污染
- 仅当原目标和更新源都是数组时,才按索引递归合并元素:
对象属性的完整保留:
- 先复制原对象的所有属性到新对象,再遍历更新源的属性进行递归合并,确保原对象属性不会被覆盖丢失
基本类型的更新逻辑:
- 更新源为基本类型时,直接覆盖原目标值,符合常规数据更新的预期
测试示例
对象数组合并(保留原属性)
const original = { users: [ { id: 1, name: 'Alice', connections: ['a', 'b'] }, { id: 2, name: 'Bob' } ] }; const update = { users: [ { id: 1, age: 30 } ] }; const result = deepMerge(original, update); // result.users[0] → { id:1, name:'Alice', connections:['a','b'], age:30 } // result.users[1] → { id:2, name:'Bob' }(完整保留)
字符串数组(避免转成对象)
const original = { tags: ['js', 'mongodb'] }; const update = { tags: ['node'] }; const result = deepMerge(original, update); // result.tags → ['node', 'mongodb'](未被转成索引对象)
问题根源分析
- 原数组对象属性丢失:之前的函数未正确处理数组内的对象,直接替换了数组元素而非递归合并对象属性
- 字符串数组转索引对象:之前的函数误将数组当作普通对象处理,遍历索引作为对象键,导致数组被转换为键值对结构
内容的提问来源于stack exchange,提问作者Marc
相关产品推荐
相关产品推荐

