人人都会AI编程

Set 与数组的对比与选型

更新时间:2026-07-11

数组是 JavaScript 中最常用的数据结构,但 ES6 引入的 Set 在许多场景下是更优的选择。两者看似都能存储一组值,底层设计和适用场景却有本质区别。选对数据结构,往往能让代码更清晰、性能更好。

核心差异

| 维度 | 数组 (Array) | Set |
|------|-------------|-----|
| 唯一性 | 允许重复值 | 自动去重,值唯一 |
| 顺序 | 严格有序,通过索引访问 | 有序(插入顺序),但无索引 |
| 查找 | indexOf / includes,O(n) | has,平均 O(1) |
| 插入 | push / unshift / splice | add |
| 删除 | splice / filter,需知道索引 | delete,直接按值删除 |
| 大小 | length 属性 | size 属性 |
| 遍历 | forEachfor...ofmap 等 | forEachfor...of |
| 典型场景 | 有序集合、栈/队列、映射转换 | 去重、存在性检查、集合运算 |

性能对比(定性)

  • 存在性检查Set.has() 基于哈希实现,不受元素数量影响;Array.includes() 需要遍历整个数组,元素越多越慢。当需要频繁判断“某个值是否存在”时,Set 的性能优势极其明显。
  • 添加与删除:Set 的 adddelete 都按值操作,而数组的 splice 需要先找到索引。在处理动态集合(频繁增删)时,Set 代码更简洁,性能也通常更好。
  • 内存占用:Set 内部使用哈希表存储唯一值,相比数组会有一定的内存开销。在小数据量场景下差异可忽略;在极大量数据(百万级别)且不需要唯一性保证时,数组可能更省内存。
  • 按索引访问:数组可以直接 arr[0],而 Set 没有索引概念,无法直接取“第 n 个”。如果需要按位置操作,数组是唯一选择。

实际代码对比

// 去重:Set 一行搞定
const arr = [1, 2, 2, 3];
const uniqueArr = [...new Set(arr)]; // [1, 2, 3]

// 存在性检查
// 数组每次都要遍历
if (arr.includes(someValue)) { ... }

// Set 直接查哈希
const set = new Set(arr);
if (set.has(someValue)) { ... }

// 删除指定值
// 数组需要先找索引再删除
const index = arr.indexOf(value);
if (index > -1) arr.splice(index, 1);

// Set 直接按值删除
set.delete(value);

选型建议

优先选 Set 的场景

  • 需要集合中的元素唯一(去重、标签集合、ID 集合)。
  • 频繁执行存在性检查has),且数据量较大。
  • 需要高效的按值增删,不关心位置。
  • 想要进行集合运算(交集、并集、差集)——Set 可以配合简短的函数实现。

继续用数组的场景

  • 需要有序存储并依赖索引访问(如列表渲染、分页)。
  • 需要重复元素(如统计频次、购物车数量)。
  • 需要大量数组特有的方法链(mapfilterreduce)。
  • 需要对数据进行排序(数组有 sort,Set 需先转为数组)。
  • 与现有代码兼容——很多库和旧接口返回的是数组。

真实项目中的权衡

  • 集合运算示例:可以用 new Set([...setA].filter(x => setB.has(x))) 实现交集,但数据量极大时需注意转换开销。对于百万级数据,原生 Set 操作仍然很快,转为数组再转回 Set 会带来额外内存和遍历成本,应尽量直接迭代 Set。
  • 内存敏感场景:如果仅需要临时去重后立即转为数组使用,[...new Set(array)] 是简洁且性能可接受的写法,Set 中间态会被垃圾回收。

一个常见误区的澄清

“Set 比数组更快”这个说法并不全面。Set 在按值存取上确实快,但若你的需求是遍历所有元素进行转换,数组的 map 原生优化同样出色,且无需额外哈希计算。数据结构没有绝对好坏,只有是否匹配当前操作模式。根据实际的读写比例和访问模式选择,才是最务实的做法。