JS 数组对象合并实战:从基础实现到细节优化
在日常前端开发中,我们经常会遇到数组对象的分组合并需求,尤其是涉及父级与子集关联的数据处理场景。最近我就遇到了一个这样的需求,通过多轮调整优化,最终实现了符合预期的功能,今天就把整个开发过程记录下来,分享给有需要的同行。

需求初始场景很明确,后端返回的原始数据是一个二维数组,每一项数组中包含两个对象,第一项是父级数据,第二项是子集数据,具体格式如下:
const originalData = [
[{"id":1764,"name":"园艺","industry_design":1},{"id":1766,"name":"花境工程","industry_design":1}],
[{"id":1764,"name":"园艺","industry_design":1},{"id":1767,"name":"屋顶花园","industry_design":1}],
[{"id":1764,"name":"园艺","industry_design":1},{"id":1768,"name":"阳台景观","industry_design":1}],
[{"id":1751,"name":"花园庭院","industry_design":2},{"id":1757,"name":"1233333","industry_design":1}]
];需要将这些数据按父级分组,合并相同父级的所有子集,最终返回指定格式:父级 id、父级 name,以及包含所有子级名称的 children_text 数组,格式要求如下:
[
{id:'父级 id', name: '父级 name', children_text:['子级 1 name','子级 2 name'] },
...
]拿到需求后,我首先梳理了核心实现思路:既然要按父级分组,最高效的方式就是以父级 id 作为唯一标识,通过一个临时对象存储分组数据,避免重复创建父级条目。接着遍历原始数组,提取每一项中的父级和子级信息,将相同父级的子级名称收集到对应的数组中,最后将临时对象转换为目标格式的数组。 基于这个思路,我编写了第一版实现代码:
function mergeArrayObjects(data) {
const tempMap = {};
data.forEach(item => {
const parent = item[0];
const child = item[1];
if (!tempMap[parent.id]) {
tempMap[parent.id] = {
id: parent.id,
name: parent.name,
children_text: []
};
}
if (!tempMap[parent.id].children_text.includes(child.name)) {
tempMap[parent.id].children_text.push(child.name);
}
});
return Object.values(tempMap);
}这段代码的核心逻辑很清晰:通过 tempMap 以父级 id 为键,存储父级信息和对应的子级名称数组;遍历过程中,若父级未在 tempMap 中,则初始化包含 id、name 和空 children_text 数组的对象;之后将子级名称去重后添加到对应父级的 children_text 中,最后通过 Object.values() 将 tempMap 转换为数组返回。 测试后发现,这段代码能满足基础需求,对于原始数据中的父级 园艺 和 花园庭院 ,能正确合并其子级,输出结果如下:
[
{ id: 1764, name: '园艺', children_text: ['花境工程', '屋顶花园', '阳台景观'] },
{ id: 1751, name: '花园庭院', children_text: ['1233333'] }
]但在实际测试过程中,我发现了一个潜在问题:需求中没有明确说明子级一定存在,若后端返回的数据中,某一项只有父级、没有子级(即数组长度为 1),那么这段代码会报错,因为 child 会是 undefined,调用 child.name 会触发异常。同时,即使不报错,无子级的父级也会被添加一个空的 children_text 数组,这不符合 无子集则不添加该属性 的隐性需求。 针对这个问题,我对代码进行了优化,核心修改点是增加子级存在性判断,仅在子级有效时才添加 children_text 属性,具体优化后的代码如下:
function mergeArrayObjects(data) {
const tempMap = {};
data.forEach(item => {
const parent = item[0];
const child = item[1];
if (!tempMap[parent.id]) {
tempMap[parent.id] = {
id: parent.id,
name: parent.name
};
}
if (child && child.name) {
if (!tempMap[parent.id].children_text) {
tempMap[parent.id].children_text = [];
}
if (!tempMap[parent.id].children_text.includes(child.name)) {
tempMap[parent.id].children_text.push(child.name);
}
}
});
return Object.values(tempMap);
}这次优化主要做了三个调整:一是初始化父级时,不再预设 children_text 属性,仅保留 id 和 name 两个核心属性;二是增加了子级有效性判断,通过 if (child && child.name) 确保子级存在且有有效的 name 属性,避免 undefined 报错;三是只有在子级有效时,才检查并创建 children_text 数组,再添加子级名称,确保无子级的父级不会出现多余的 children_text 属性。 为了验证优化效果,我补充了无子级的测试数据,新增了一个只有父级 绿植租赁 的条目:
const originalData = [
[{"id":1764,"name":"园艺","industry_design":1},{"id":1766,"name":"花境工程","industry_design":1}],
[{"id":1764,"name":"园艺","industry_design":1},{"id":1767,"name":"屋顶花园","industry_design":1}],
[{"id":1764,"name":"园艺","industry_design":1},{"id":1768,"name":"阳台景观","industry_design":1}],
[{"id":1751,"name":"花园庭院","industry_design":2},{"id":1757,"name":"1233333","industry_design":1}],
[{"id":1780,"name":"绿植租赁","industry_design":3}] // 无子级
];再次调用函数,输出结果完全符合预期:有子级的父级包含 children_text 属性,无子级的父级则不包含该属性,且无任何报错:
[
{ id: 1764, name: '园艺', children_text: ['花境工程', '屋顶花园', '阳台景观'] },
{ id: 1751, name: '花园庭院', children_text: ['1233333'] },
{ id: 1780, name: '绿植租赁' }
]到这里,整个需求就完全实现了。回顾整个开发过程,从最初的基础实现,到后续发现潜在问题并进行细节优化,核心始终围绕 满足需求、提升健壮性 展开。其实很多前端开发中的数据处理场景,都需要我们不仅实现基础功能,还要考虑到边界情况,比如数据缺失、格式异常等,这样才能写出更可靠、更易维护的代码。
本次实战中,临时对象分组的思路不仅高效,而且易于理解和维护,适合处理类似的分组合并需求;同时,子级存在性的判断,也让代码更加健壮,避免了因数据异常导致的报错。希望这段开发经历,能给遇到类似需求的同行提供一些参考。




