在javascript中合并数组并去重,最推荐的方法是使用set结合展开运算符,1. 对于基本数据类型,直接使用[...new set([...arr1, ...arr2])]即可高效去重;2. 对于对象数组,需基于唯一标识属性利用map实现去重,如通过对象的id作为key进行覆盖或保留策略;3. 当无唯一标识时,可采用深比较或序列化为字符串的方式,但需注意性能开销与属性顺序、循环引用等限制;该方法之所以首选,是因为set具有原生高效、语义清晰和api简洁的优势,其局限性主要体现在对象引用比较、老旧浏览器兼容性及复杂对象去重需自定义逻辑等方面,因此处理基本类型时推荐使用set,处理复杂对象时应结合map或深比较策略以确保正确性。

在JavaScript中,要合并数组并去除重复项,最直接且推荐的方法是利用
Set
...
Set
谈到JavaScript里数组的“并集”操作,并且还要去重,我个人觉得,
Set
方法一:利用 Set
这是我处理这类问题时的首选。思路很简单:先把所有数组扁平化到一个临时数组里,然后把这个临时数组扔进
Set
Set
const arr1 = [1, 2, 3, 4]; const arr2 = [3, 4, 5, 6]; const arr3 = [6, 7, 8, 1]; // 步骤:合并所有数组,创建Set,再转回数组 const combinedAndUniqueArray = Array.from(new Set([...arr1, ...arr2, ...arr3])); console.log(combinedAndUniqueArray); // 输出: [1, 2, 3, 4, 5, 6, 7, 8] // 或者更简洁一点,如果只合并两个: const newArr1 = [1, 2, 3]; const newArr2 = [3, 4, 5]; const unionSetResult = [...new Set([...newArr1, ...newArr2])]; console.log(unionSetResult); // 输出: [1, 2, 3, 4, 5]
这种方式对于基本数据类型(字符串、数字、布尔值、
null
undefined
Symbol
方法二:手动迭代与 indexOf
includes
在
Set
Set
Set
function mergeAndDeduplicateManual(arrA, arrB) {
const result = [...arrA]; // 先把第一个数组的元素都放进来
for (const item of arrB) {
if (result.indexOf(item) === -1) { // 如果结果数组中还没有这个元素
result.push(item); // 就加进去
}
}
return result;
}
const manualArr1 = [10, 20, 30];
const manualArr2 = [20, 40, 50];
console.log(mergeAndDeduplicateManual(manualArr1, manualArr2)); // 输出: [10, 20, 30, 40, 50]
// 多个数组的话,可以链式调用或者用 reduce
const allArrays = [[1, 2], [2, 3], [3, 4]];
const mergedAllManual = allArrays.reduce((acc, currentArr) => {
for (const item of currentArr) {
if (!acc.includes(item)) {
acc.push(item);
}
}
return acc;
}, []);
console.log(mergedAllManual); // 输出: [1, 2, 3, 4]每次
indexOf
includes
result
O(N)
O(N*M)
Set
add
O(1)
方法三:利用 reduce
Set
这个方法其实是
Set
reduce
const list1 = [1, 2, 3];
const list2 = [3, 4, 5];
const list3 = [5, 6, 7];
const allLists = [list1, list2, list3];
const uniqueCombined = allLists.reduce((accSet, currentArray) => {
currentArray.forEach(item => accSet.add(item)); // 将当前数组的每个元素添加到Set中
return accSet;
}, new Set()); // 初始值是一个空的Set
console.log(Array.from(uniqueCombined)); // 输出: [1, 2, 3, 4, 5, 6, 7]我个人觉得,当你有一堆数组需要合并去重时,
reduce
Set
Set
在我看来,JavaScript中合并数组并去重的“最佳实践”很大程度上取决于你数组里装的是什么东西。如果都是基本数据类型(数字、字符串、布尔值等),那毫无疑问,
Set
const numbers1 = [1, 2, 3, 2]; const numbers2 = [3, 4, 5, 4]; const uniqueNumbers = [...new Set([...numbers1, ...numbers2])]; console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
但如果你的数组里包含了复杂对象,事情就变得有点意思了。
Set
Set
const obj1 = { id: 1, name: 'A' };
const obj2 = { id: 2, name: 'B' };
const obj3 = { id: 1, name: 'A' }; // 内容和obj1一样,但引用不同
const objectArray = [obj1, obj2, obj3];
const uniqueObjectsByReference = [...new Set(objectArray)];
console.log(uniqueObjectsByReference);
// 输出: [{ id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 1, name: 'A' }]
// 注意,obj1和obj3都被保留了,因为它们是不同的对象实例这种情况下,你需要定义自己的“唯一性”标准。通常,我们会基于对象的一个或多个属性来判断。比如,如果你的对象都有一个唯一的
id
Map
reduce
const users1 = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const users2 = [{ id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }];
// 使用 Map 来存储唯一的对象,key是id
const uniqueUsersMap = new Map();
[...users1, ...users2].forEach(user => {
uniqueUsersMap.set(user.id, user); // 如果id相同,新的会覆盖旧的
});
const uniqueUsers = Array.from(uniqueUsersMap.values());
console.log(uniqueUsers);
// 输出: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]我个人觉得,处理对象数组时,这种基于
Map
Set
Set
add
delete
has
O(1)
indexOf
includes
O(N)
Set
Set
Set
filter
reduce
...
Array.from()
// 简洁且高效 const arrA = ['apple', 'banana']; const arrB = ['banana', 'orange']; const result = [...new Set([...arrA, ...arrB])]; console.log(result); // ['apple', 'banana', 'orange']
然而,
Set
对象引用问题: 这是
Set
Set
Set
const item1 = { name: 'Book', price: 20 };
const item2 = { name: 'Book', price: 20 }; // 内容相同,但不是同一个对象实例
const uniqueItems = [...new Set([item1, item2])];
console.log(uniqueItems.length); // 2,因为item1和item2引用不同这在处理需要基于内容去重的复杂数据结构时,会让你感到有些头疼。
顺序稳定性(部分情况): 虽然现代JavaScript引擎通常会保持
Set
Set
老旧环境兼容性: 如果你的项目需要支持非常老旧的浏览器(比如IE11),那么
Set
polyfill
所以,用
Set
处理包含复杂对象的数组去重,确实比处理基本类型要复杂一些,因为我们不能简单依赖
Set
策略一:利用对象的唯一标识属性(最常用且推荐)
如果你的对象有一个或多个属性可以作为其唯一标识(比如
id
sku
uuid
Map
const productsA = [{ id: 'p001', name: 'Laptop' }, { id: 'p002', name: 'Mouse' }];
const productsB = [{ id: 'p002', name: 'Mouse Pro' }, { id: 'p003', name: 'Keyboard' }];
// 创建一个Map,以id为key,对象为value
const uniqueProductsMap = new Map();
// 合并所有产品,并用Map去重
[...productsA, ...productsB].forEach(product => {
// Map的set方法会覆盖同名的key,这正是我们想要的去重效果
// 这里选择保留后出现的重复项(Mouse Pro会覆盖Mouse)
uniqueProductsMap.set(product.id, product);
});
const uniqueProducts = Array.from(uniqueProductsMap.values());
console.log(uniqueProducts);
/*
输出:
[
{ id: 'p001', name: 'Laptop' },
{ id: 'p002', name: 'Mouse Pro' }, // 注意这里是Mouse Pro,因为后来的覆盖了
{ id: 'p003', name: 'Keyboard' }
]
*/
// 如果你想保留先出现的重复项,可以这样写:
const uniqueProductsMapPreserveFirst = new Map();
[...productsA, ...productsB].forEach(product => {
if (!uniqueProductsMapPreserveFirst.has(product.id)) {
uniqueProductsMapPreserveFirst.set(product.id, product);
}
});
const uniqueProductsPreserveFirst = Array.from(uniqueProductsMapPreserveFirst.values());
console.log(uniqueProductsPreserveFirst);
/*
输出:
[
{ id: 'p001', name: 'Laptop' },
{ id: 'p002', name: 'Mouse' }, // 这里是Mouse,因为先出现的被保留了
{ id: 'p003', name: 'Keyboard' }
]
*/这种方式非常灵活,你可以根据业务需求决定是保留第一次出现的重复项,还是最后一次出现的。
策略二:深比较(Deep Comparison)
如果你的对象没有一个简单的唯一
id
// 这是一个简化的深比较函数示例,实际情况可能需要更健壮的库
function isEqualDeep(obj1, obj2) {
if (obj1 === obj2) return true; // 相同引用或基本类型值相等
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false; // 类型不同或不是对象
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!keys2.includes(key) || !isEqualDeep(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
const itemA = { name: 'Shirt', color: 'blue', sizes: ['M', 'L'] };
const itemB = { name: 'Shirt', color: 'red', sizes: ['M', 'L'] };
const itemC = { name: 'Shirt', color: 'blue', sizes: ['M', 'L'] }; // 内容与itemA相同
const clothes = [itemA, itemB, itemC];
const uniqueClothes = [];
clothes.forEach(currentItem => {
const isDuplicate = uniqueClothes.some(existingItem => isEqualDeep(existingItem, currentItem));
if (!isDuplicate) {
uniqueClothes.push(currentItem);
}
});
console.log(uniqueClothes);
/*
输出:
[
{ name: 'Shirt', color: 'blue', sizes: ['M', 'L'] },
{ name: 'Shirt', color: 'red', sizes: ['M', 'L'] }
]
*/深比较的缺点是性能开销较大,特别是当对象结构复杂或数组非常大时。在生产环境中,我通常会倾向于使用成熟的库(比如Lodash的
_.isEqual
策略三:序列化为字符串再去重(简单但有局限性)
对于那些结构相对简单,且属性顺序不影响比较的对象,可以考虑将其序列化为JSON字符串,然后利用
Set
const data1 = { a: 1, b: 'x' };
const data2 = { b: 'x', a: 1 }; // 属性顺序不同,但内容相同
const data3 = { a: 2, b: 'y' };
const rawData = [data1, data2, data3];
// 注意:JSON.stringify对属性顺序敏感,这可能导致去重失败
// 比如 {a:1, b:2} 和 {b:2, a:1} 序列化结果不同
const uniqueStrings = new Set(rawData.map(item => JSON.stringify(item)));
const finalUniqueData = Array.from(uniqueStrings).map(str => JSON.parse(str));
console.log(finalUniqueData);
/*
输出:
[
{ a: 1, b: 'x' },
{ b: 'x', a: 1 }, // data1和data2都被保留了,因为JSON.stringify对属性顺序敏感
{ a: 2, b: 'y' }
]
*/
// 如果要解决属性顺序问题,可能需要先对对象属性进行排序
function stableStringify(obj) {
return JSON.stringify(Object.keys(obj).sort().reduce((sortedObj, key) => {
sortedObj[key] = obj[key];
return sortedObj;
}, {}));
}
const uniqueStringsStable = new Set(rawData.map(item => stableStringify(item)));
const finalUniqueDataStable = Array.from(uniqueStringsStable).map(str => JSON.parse(str));
console.log(finalUniqueDataStable);
/*
输出:
[
{ a: 1, b: 'x' }, // data1和data2现在被视为重复,只保留了一个
{ a: 2, b: 'y' }
]
*/这个方法虽然看起来简单,但它的局限性非常明显:
JSON.stringify
stableStringify
JSON.stringify
undefined
Symbol
null
所以,我通常只会在对象结构非常简单、确定没有特殊类型和循环引用,且对性能有较高要求时,才会考虑这种“暴力”的序列化去重法。大多数时候,基于唯一标识的
Map
以上就是js 如何用union合并数组并去重的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号