数组去重,不止一种姿势:JavaScript的六种高效方法,你知道几种?
最近几天,在捣鼓一个小型项目时,遇到了一个关于数组去重的问题。在这个项目中,我需要从一个包含大量数据的数组中去除重复项。因为前端页面,这里使用JavaScript的数组方法来处理这个任务,但发现有多种不同的去重方法,例如使用Set对象或filter方法。这种情况下,我该如何选择一种高效的数组去重方法来优化性能呢?
方法一:使用 Set
对象
Set
对象只允许存储唯一的值,因为它使用哈希表来存储元素,在插入和查找操作上具有较高的效率。我们可以通过将数组转换为 Set
对象,然后再转回数组来实现去重复。
let array = [1, 2, 2, 3, 4, 4, 5, 5, 5];
let uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
特点
-
仅需一行代码即可完成去重,提高了代码的可读性和维护性。
-
Set
内部机制优化了成员的唯一性检查,对于基本类型的数据去重非常快速。 -
可以处理所有基本类型的元素。如果数组包含对象元素(非基本类型),
Set
只能通过引用来判断唯一性,而不能对对象的属性进行深度比较。因此即使内容相同也会被视为不同元素,需要额外处理。
性能特点
-
对于基础类型(如数字、字符串),性能非常优秀。
-
适用于大规模数据。
适用场景
适用于大规模数据。当数组元素为基本类型(如数字、字符串)时,特别是在需要高性能去重的场景下,如数据预处理、数据分析等。
方法二:使用 filter()
方法
filter()
方法可以创建一个新数组,该数组中的元素满足指定的测试条件。我们可以利用这个方法来检查每个元素是否已存在于新数组中。
let array = [1, 2, 2, 3, 4, 4, 5, 5, 5];
let uniqueArray = array.filter((value, index, self) => {
return self.indexOf(value) === index;
});
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
特点
-
代码直观,逻辑清晰,代码可读性好。
-
通过
indexOf
检查元素首次出现的位置,适用于任何类型的数组。
性能特点
-
对于小型和中型数组,性能尚可。
-
对于基础类型数据性能一般。
-
对于复杂类型数据(如对象),每次查找都会进行全数组遍历,性能较差。
-
filter()
方法结合indexOf()
,性能主要取决于indexOf()
的效率。因为indexOf()
需要遍历数组来查找元素,所以在处理大规模数据时性能较差。
适用场景
适用于小型或中型数组,特别是当代码的可读性和维护性比性能更重要时,如前端界面数据处理、简单数据筛选等。
方法三:使用 reduce()
方法
reduce()
方法对累加器和数组中的每个元素(从左到右)应用一个函数,并将其减少为单个输出值。这个方法也可以用来去重复。
let array = [1, 2, 2, 3, 4, 4, 5, 5, 5];
let uniqueArray = array.reduce((accumulator, currentValue) => {
if (!accumulator.includes(currentValue)) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
特点
-
可以在去重的同时进行其他数据转换或计算。
-
通过累加器自定义逻辑,适应更复杂的业务场景。
性能特点
-
适合中小型数组,代码可读性高。
-
对基础类型数据性能一般。
-
对复杂类型数据(如对象)性能较差,因为每次都会全数组遍历。
-
filter()
方法结合indexOf()
,性能主要取决于indexOf()
的效率。因为indexOf()
需要遍历数组来查找元素,所以在处理大规模数据时性能较差。
适用场景
当需要在去重过程中同时执行其他逻辑操作,比如计算累计值、过滤特定条件等,适合于数据处理管道式操作。
方法四:使用 Map
对象
类似于 Set
对象,Map
对象也可以用来去重复,但这种方法稍微复杂一些。Map
对象在查找和插入操作上的效率较高,因为它也是基于哈希表实现的。
let array = [1, 2, 2, 3, 4, 4, 5, 5, 5];
let map = new Map();
let uniqueArray = array.filter(value => {
return map.has(value) ? false : (map.set(value, true), true);
});
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
特点
-
相较于
indexOf
或includes
,使用Map
的查找操作更快。 -
可以利用
Map
的键值对特性存储额外信息。 -
实现相对其他方法更为复杂,不易于初学者理解。
性能特点
-
对于基础类型数据性能优异。
-
适用于大规模数据。
-
对复杂类型数据(如对象),同样只能通过引用来判断唯一性,无法进行深度比较。
适用场景
适用于对查找性能有较高要求的场景,或者需要利用 Map
的附加功能,如关联额外数据到每个唯一元素上。
方法五:使用 indexOf
和 concat
方法
这种方法与 reduce
类似,但是使用 concat
来构建新数组。
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((accumulator, current) => {
if (accumulator.indexOf(current) === -1) {
return accumulator.concat(current);
}
return accumulator;
}, []);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
特点
-
合了
indexOf
的直观性与concat
的便捷性,代码逻辑清晰。 -
由于
indexOf
的线性查找,不适用于大规模数据集。
性能特点
-
适合中小型数组,代码易于理解。
-
对基础类型数据性能一般。
-
对复杂类型数据(如对象)性能较差。
适用场景
适合小型数组的简单去重操作,尤其是在不需要考虑高性能,且希望代码保持简单直观的情况下。
方法六:使用 Object
作为映射
这种方法利用了对象属性名称的唯一性。利用对象属性名的唯一性,性能在处理基础类型数据时较好,但对于复杂类型数据,需要考虑对象属性的转化。
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Object.keys(array.reduce((accumulator, current) => {
if (!accumulator[current]) {
accumulator[current] = true;
}
return accumulator;
}, {}));
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
特点
-
利用对象键的唯一性,简单直接地实现去重。
-
在不支持
Set
的旧环境中依然可用。 -
不适用于对象元素的去重,因为对象作为键会被转换为字符串。
-
相较于
Set
或filter()
,可读性较差。
性能特点
-
对基础类型数据性能较好。
-
不适用于大规模数据,因为对象属性数量有限。
-
对复杂类型数据(如对象),需要先将对象转化为字符串键,性能和可读性较差
适用场景
当需要在老版本浏览器或不支持现代ECMAScript特性的环境中实现基本类型数组的去重时。
总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
使用 Set 对象 | - 简洁明了- 性能优秀,适合处理大量数据 | - 只适用于元素为基本类型的数组 | 基本类型元素数组的去重,性能要求高的场景 |
使用 filter() 方法 | - 代码直观,易于理解 | - indexOf 在大型数组上效率较低 | 小型或中型数组,代码可读性要求高的场景 |
使用 reduce() 方法 | - 灵活性高,可以在去重的同时进行其他操作 | - includes 在大型数组上效率较低 | 需要在去重的同时进行其他操作的场景 |
使用 Map 对象 | - 提高了查找效率 | - 代码相对复杂,理解起来不如 Set 直观 | 需要兼顾去重和查找效率的场景 |
使用 indexOf 和 concat 方法 | - 与 reduce 类似,易于理解- 适用于所有类型的数组 | - indexOf 的性能问题依旧存在 | 需要一个简单且易于理解的去重实现 |
使用 Object 作为映射 | - 利用了对象属性名唯一性- 对于基本类型的数组性能较好 | - 只适用于元素为基本类型的数组- 代码可读性略差 | 需要一个性能较好的基本类型数组去重方法 |
在日常开发中,Set
通常是最推荐的方法,但其他方法也有其独特的适用场景。
转载自:https://juejin.cn/post/7378459137418936320