在 JavaScript 开发中,我们经常需要合并两个数组并去除重复项。一个常见的解决方案是使用扩展运算符
和 Set
数据结构:
const uniqueArray = Array.from(new Set([...value, ...ids]));
这行简洁的代码实现了数组合并与去重的功能。本文将深入解析这一技巧,补充相关基础知识,并详细讨论其优缺点。
基础知识补充
1. 数组基础
JavaScript 数组是一种特殊的对象,用于存储有序
的数据集合。与普通对象不同,数组的键是数字索引,从 0 开始。
const fruits = ['apple', 'banana', 'orange'];
// fruits[0] => 'apple'
// fruits.length => 3
2. Set 数据结构
Set
是 ES6 引入的一种新的数据结构,它类似于数组,但成员的值都是唯一的,没有重复的值。
const set = new Set([1, 2, 2, 3, 4, 4]);
console.log(set); // Set {1, 2, 3, 4}
Set 的主要特点:
- 自动去重
- 可以快速判断元素是否存在 (
has
方法) - 可以获取元素数量 (
size
属性)
3. Array.from 方法
Array.from()
方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
// 从类数组对象创建数组
const arrayLike = {0: 'a', 1: 'b', length: 2};
const arr1 = Array.from(arrayLike); // ['a', 'b']
// 从可迭代对象创建数组
const set = new Set(['a', 'b', 'c']);
const arr2 = Array.from(set); // ['a', 'b', 'c']
代码逐步解析
让我们详细分解这行代码:
const uniqueArray = Array.from(new Set([...value, ...ids]));
第一步:扩展运算符合并数组 […value, …ids]
扩展运算符 (...)
允许一个可迭代对象(如数组或字符串)在需要多个元素的地方展开。
const value = [1, 2];
const ids = [2, 3];
const merged = [...value, ...ids]; // [1, 2, 2, 3]
优点:
- 语法简洁,比 concat 方法更直观
- 可以轻松合并多个数组
- 可以与其他解构操作结合使用
缺点:
- 创建了一个新的中间数组,可能会对性能有轻微影响(对于非常大的数组)
第二步:创建 Set 去重 new Set([…value, …ids])
将合并后的数组传递给 Set 构造函数
,Set 会自动去除重复项。
const merged = [1, 2, 2, 3];
const uniqueSet = new Set(merged); // Set {1, 2, 3}
优点:
- 自动去重,无需手动实现
- 去重操作非常高效
- 代码简洁
缺点:
Set
不保留元素的原始顺序(但实际上会保留首次出现的顺序)Set
不是数组,不能直接使用数组方法
第三步:转换回数组 Array.from(new Set([…value, …ids]))
使用 Array.from()
将 Set 转换回数组。
const uniqueSet = new Set([1, 2, 3]);
const uniqueArray = Array.from(uniqueSet); // [1, 2, 3]
优点:
- 得到的是标准的数组,可以使用所有数组方法
- 代码清晰表达意图
缺点:
- 需要额外的转换步骤
替代方案比较
1. 使用 filter 和 indexOf (传统方法)
const uniqueArray = [...value, ...ids].filter((item, index, array) => array.indexOf(item) === index);
缺点:
- 性能较差,特别是对于大数组,因为
indexOf
在每次迭代中都要遍历数组 - 代码较冗长
2. 使用 reduce 方法
const uniqueArray = [...value, ...ids].reduce((acc, current) => {
if (!acc.includes(current)) {
acc.push(current);
}
return acc;
}, []);
缺点:
- 代码较复杂
includes
方法在每次迭代中都要检查数组,性能不如Set
3. 使用 lodash 的 uniq 方法
const _ = require('lodash');
const uniqueArray = _.uniq([...value, ...ids]);
优点:
- 代码简洁
- 经过充分测试,可靠性高
缺点:
- 需要引入额外的库
- 对于简单任务可能过度设计
性能考虑
对于小到中等大小的数组,[...value, ...ids]
方法性能很好。但对于非常大的数组(数万或更多元素),可能需要考虑:
- 如果顺序不重要,可以先将数组转换为 Set 再转换回来,可能更快
- 如果内存是瓶颈,可以考虑流式处理或分批处理
实际应用场景
这种数组合并去重技巧在以下场景中特别有用:
- 合并用户ID列表:从不同来源获取的用户ID合并并去重
- 标签系统:合并多个标签数组并确保没有重复标签
- 配置合并:合并默认配置和用户自定义配置
- 数据聚合:从多个API响应中合并数据并去重
注意事项
- 类型一致性:确保合并的数组元素类型一致,否则可能无法正确去重
const a = [1, '1']; // 数字1和字符串'1'会被视为不同
const unique = Array.from(new Set([...a])); // [1, '1']
- 对象引用:Set 使用严格相等(===)比较,所以对象即使内容相同也会被视为不同
const obj1 = {id: 1};
const obj2 = {id: 1};
const arr = [obj1, obj2];
const unique = Array.from(new Set(arr)); // [obj1, obj2]
- 非数组输入:如果 value 或 ids 不是数组,代码会抛出错误
const value = 'not an array';
const ids = [1, 2];
const unique = Array.from(new Set([...value, ...ids])); // TypeError
结论
[...value, ...ids]
这种数组合并去重技巧是现代 JavaScript 开发中的一个强大工具。它结合了扩展运算符的简洁性、Set 的高效去重能力和 Array.from
的灵活转换,提供了一种优雅的解决方案。
优点总结:
- 代码简洁易读
- 性能良好
- 保留首次出现的顺序
- 纯函数式,不修改原数组
缺点总结:
- 创建中间数组可能对极大数组有性能影响
- 不适用于非数组输入(需要额外处理)
- 对象比较基于引用而非内容
在实际开发中,根据具体场景选择最适合的方法。对于大多数情况,这种扩展运算符加 Set 的方法已经足够好,也是当前 JavaScript 社区中的推荐做法。
推荐更多阅读内容
如何让 Linux 主机“隐身”:禁用 Ping 响应
深入理解 CSS 高度塌陷问题及解决方案
深入理解 JavaScript的空值合并运算符(提升代码精确性)
深入理解 JavaScript 的可选链操作符(提升代码健壮性和可维护性)
解决Antd Form组件初次渲染时禁用交互逻辑失效的问题(说明初始值的重要性)
:is() 伪类选择器(使代码更简洁)