JavaScript合并两个数组对象并根据ID去重

6 次阅读

JavaScript开发过程中,经常会遇到多个数组对象合并的需求。例如接口返回两批用户数据、商品列表分页加载、权限集合合并等场景,都需要将两个数组合并成一个完整的数据集合。同时,由于不同数据源可能存在重复记录,通常还需要根据对象中的唯一标识字段(如ID)进行去重。

本文将详细介绍JavaScript中合并两个数组对象并根据ID去重的多种实现方式,包括ES6常用方法、Map数据结构处理方案以及传统循环方式,帮助开发者根据实际业务需求选择合适的方案。

一、需求分析:合并数组对象并按照ID去重

假设有两个用户数组:

JavaScript
const arr1 = [
    { id: 1, name: "张三", age: 20 },
    { id: 2, name: "李四", age: 25 },
    { id: 3, name: "王五", age: 30 }
];

const arr2 = [
    { id: 2, name: "李四", age: 25 },
    { id: 3, name: "王五", age: 31 },
    { id: 4, name: "赵六", age: 28 }
];

两个数组合并后,希望得到:

JavaScript
[
    { id: 1, name: "张三", age: 20 },
    { id: 2, name: "李四", age: 25 },
    { id: 3, name: "王五", age: 30 },
    { id: 4, name: "赵六", age: 28 }
]

其中:

  • id为1的数据保留;

  • id为2、3的数据只保留一份;

  • id为4的数据新增;

  • 相同ID的数据根据规则决定保留哪一个。


二、使用Map实现数组对象根据ID去重(推荐)

ES6中的Map结构非常适合处理对象数组去重问题。

Map具有键唯一的特点,如果设置相同key,后面的值会覆盖前面的值,因此可以直接利用对象ID作为Map的key。

示例:

JavaScript
const result = Array.from(
    new Map(
        [...arr1, ...arr2].map(item => [item.id, item])
    ).values()
);

console.log(result);

执行过程:

  1. 使用扩展运算符合并两个数组:

JavaScript
[...arr1, ...arr2]

得到:

JavaScript
[
    {id:1},
    {id:2},
    {id:3},
    {id:2},
    {id:3},
    {id:4}
]
  1. 使用map转换为Map键值结构:

JavaScript
[item.id, item]

例如:

JavaScript
[
    [1, {id:1}],
    [2, {id:2}],
    [3, {id:3}]
]
  1. Map自动根据ID去重。

  2. 使用values()获取最终对象集合。

这种方式代码简洁,执行效率较高,是现代JavaScript项目中比较推荐的写法。


三、根据ID去重并保留后面的数据

Map默认会覆盖前面的数据,因此:

JavaScript
const arr = [
    {id:1,name:"A"},
    {id:1,name:"B"}
];

const result = Array.from(
    new Map(arr.map(item => [item.id,item])).values()
);

console.log(result);

输出:

JavaScript
[
    {
        id:1,
        name:"B"
    }
]

原因是后面的对象覆盖了前面的对象。

这种方式适合:

  • 最新接口数据覆盖旧数据;

  • 后加载的数据优先;

  • 更新列表数据。

例如:

JavaScript
const merged = Array.from(
    new Map(
        [...oldList,...newList]
        .map(item => [item.id,item])
    ).values()
);

新列表中的相同ID数据会覆盖旧数据。


四、保留前面的数据,根据ID去重

有些业务场景希望第一次出现的数据优先,例如:

  • 本地缓存优先;

  • 历史数据优先;

  • 固定排序需求。

可以使用Set配合filter:

JavaScript
const merged = [...arr1, ...arr2];

const ids = new Set();

const result = merged.filter(item => {
    if(ids.has(item.id)){
        return false;
    }

    ids.add(item.id);
    return true;
});

console.log(result);

运行逻辑:

第一次遇到ID:

JavaScript
ids.has(item.id)

返回false,保留数据。

再次遇到相同ID:

返回true,过滤掉。

最终保留第一次出现的数据。


五、使用reduce实现数组对象去重

reduce也是处理数组合并去重的常用方法。

示例:

JavaScript
const result = [...arr1, ...arr2].reduce((prev, item) => {

    const exist = prev.find(
        value => value.id === item.id
    );

    if(!exist){
        prev.push(item);
    }

    return prev;

}, []);

console.log(result);

实现思路:

  1. 创建空数组;

  2. 遍历合并后的数据;

  3. 判断当前ID是否已经存在;

  4. 不存在则加入结果数组。

这种方式代码容易理解,适合初学者。

不过需要注意:

JavaScript
find()

每次都会遍历已有数组。

如果数据量较大,例如几万条数据,性能会明显低于Map方案。


六、处理多个数组对象合并去重

实际项目中可能不是两个数组,而是多个数组:

JavaScript
const list1 = [];
const list2 = [];
const list3 = [];

可以继续使用展开运算符:

JavaScript
const result = Array.from(
    new Map(
        [
            ...list1,
            ...list2,
            ...list3
        ].map(item => [
            item.id,
            item
        ])
    ).values()
);

这种方式可以无限扩展,适合处理多个接口数据合并。


七、根据指定字段进行动态去重

有时候唯一标识不是id,而可能是:

  • userId

  • productId

  • code

  • uuid

可以封装成通用函数:

JavaScript
function mergeUnique(arr1, arr2, key){

    return Array.from(
        new Map(
            [...arr1,...arr2]
            .map(item => [
                item[key],
                item
            ])
        ).values()
    );

}

调用:

JavaScript
const result = mergeUnique(
    users1,
    users2,
    "id"
);

如果按照商品编号:

JavaScript
const result = mergeUnique(
    products1,
    products2,
    "productId"
);

这样可以提高代码复用性。


八、处理ID类型不一致的问题

实际开发中,经常出现:

JavaScript
{
    id:1
}

和:

JavaScript
{
    id:"1"
}

这种情况。

Map会认为:

JavaScript
1 !== "1"

因此无法去重。

解决方式:

统一ID类型:

JavaScript
const result = Array.from(
    new Map(
        [...arr1,...arr2]
        .map(item => [
            String(item.id),
            item
        ])
    ).values()
);

这样:

JavaScript
1

和:

JavaScript
"1"

都会转换成:

JavaScript
"1"

避免重复数据。


九、数组对象合并去重时的数据覆盖策略

根据业务需求不同,重复ID的数据处理方式也不同。

1. 后者覆盖前者

适合:

  • 最新数据更新;

  • 接口同步。

实现:

JavaScript
new Map(
    [...oldData,...newData]
    .map(item=>[item.id,item])
)

2. 前者覆盖后者

适合:

  • 本地缓存优先;

  • 固定配置优先。

实现:

JavaScript
new Map(
    [...newData,...oldData]
    .map(item=>[item.id,item])
)

调整数组顺序即可。


3. 合并两个对象属性

如果两个对象ID相同,但字段不同,可以使用对象合并:

JavaScript
const result = Array.from(
    new Map(
        [...arr1,...arr2]
        .map(item=>[
            item.id,
            {
                ...item
            }
        ])
    ).values()
);

也可以:

JavaScript
const map = new Map();

[...arr1,...arr2].forEach(item=>{

    if(map.has(item.id)){

        map.set(
            item.id,
            {
                ...map.get(item.id),
                ...item
            }
        );

    }else{

        map.set(item.id,item);

    }

});

const result = [...map.values()];

这种方式适合处理复杂对象合并。


十、性能对比分析

不同去重方式性能差异:

方法时间复杂度适用场景
MapO(n)大数据量推荐
Set+filterO(n)简单去重
reduce+findO(n²)数据量较小
双重循环O(n²)传统写法

如果数组只有几十条数据,几种方式都可以。

如果数据达到:

  • 1万条;

  • 10万条;

  • 大量接口合并;

建议优先选择Map方案。


十一、实际项目中的封装示例

在Vue、React等前端项目中,可以封装公共方法:

JavaScript
export function uniqueById(list){

    return Array.from(
        new Map(
            list.map(item=>[
                item.id,
                item
            ])
        ).values()
    );

}

使用:

JavaScript
const data = uniqueById([
    ...pageData1,
    ...pageData2
]);

可以方便地处理:

  • 分页数据合并;

  • 无限滚动加载;

  • 搜索结果合并;

  • 多接口数据整合。


总结

JavaScript合并两个数组对象并根据ID去重,是前端开发中非常常见的数据处理需求。

对于现代浏览器环境,推荐使用Map结合数组展开运算符:

JavaScript
const result = Array.from(
    new Map(
        [...arr1,...arr2]
        .map(item=>[item.id,item])
    ).values()
);

该方案代码简洁、性能优秀,并且可以灵活控制重复数据的覆盖规则。

如果需要保留首次出现的数据,可以结合Set和filter实现;如果需要处理复杂对象合并,则可以在Map基础上增加对象展开逻辑。根据数据规模和业务规则选择合适的方法,可以让数组处理代码更加稳定、高效。