用 Set 实现数组并集、交集、差集可大幅提升性能:并集用 [...new Set([...a, ...b])](基础类型)或按 key 提取去重(对象数组);交集用 a.filter(x => setB.has(x));差集用 a.filter(x => !setB.has(x)),均需预建 Set 以避免 O(n²) 复杂度。

用 Set 实现数组并集时,别直接 new Set([...a, ...b])
这个写法看似简洁,但会丢失原始数组中重复元素的语义——如果业务上 a 或 b 本身含重复项(比如用户 ID 列表可能有重试导致的重复请求),而你只关心“是否出现过”,那没问题;但若后续要统计频次或保留上下文,Set 一上来就去重反而掩盖了问题。更关键的是,它无法处理对象数组:new Set([{id:1}, {id:1}]) 会认为两个对象不同,结果长度为 2。
实操建议:
- 纯数字/字符串数组求并集:用
[...new Set([...a, ...b])]是安全且高效的 - 对象数组需按字段比对(如
id):先用map提取 key,再套一层Set去重,最后通过filter回溯原数组 - 注意 V8 对超大数组(>10⁵ 元素)展开运算符的性能抖动,可改用
for...of循环 +add()
交集不能只靠 filter(x => b.includes(x)) 做判断
includes() 在 b 较大时是 O(n) 查找,整体变成 O(m×n),10 万级数组可能卡顿。用 Set 预存 b 的值,能把单次查找压到 O(1) 平均复杂度。
实操建议:
- 基础写法:
const setB = new Set(b); const intersection = a.filter(x => setB.has(x)) - 如果 a、b 都是对象数组,先构造
setB存b.map(x => x.id),再a.filter(x => setB.has(x.id)) - 注意 NaN 的陷阱:
new Set([NaN]).has(NaN)返回true,但[NaN].includes(NaN)也返回true,这点倒是一致;不过Object.is(NaN, NaN)才是真正可靠的全等判断,Set 内部用的就是Object.is
差集(a 相对于 b 的差)要分清是“a 中有、b 中没有”还是“对称差”
业务里常混淆这两个概念。“a 对 b 的差集”指 a.filter(x => !b.includes(x)),而“对称差”是 (a−b) ∪ (b−a),即双方独有的元素。前者用一个 Set 就够,后者得建两个 Set 再合并。
实操建议:
- 单向差集(推荐):
const setB = new Set(b); a.filter(x => !setB.has(x)) - 对称差集:
[...a.filter(x => !setB.has(x)), ...b.filter(x => !setA.has(x))],其中setA = new Set(a) - 如果 a、b 量级悬殊(比如 a 是 100 个用户,b 是 100 万订单 ID),优先把大数据构造成
Set,小数据来遍历,避免内存浪费
遇到嵌套结构或深比较时,Set 失效是正常现象
Set 只做引用比较或 Object.is 比较,对 {a:1} 和 {a:1} 视为不同元素。业务中常见“订单列表 diff”“配置项变更检测”,这时不能硬套 Set,得降级用 JSON.stringify 或专用库(如 lodash.isequal)预处理。
实操建议:
- 简单扁平对象可转成唯一 key:
x => `${x.id}_${x.status}`,再塞进Set - 慎用
JSON.stringify:属性顺序不一致会导致 key 不同;undefined、function、Symbol 会被忽略;循环引用直接报错 - 真需要深比较交集,不如先用
Map存key → item映射,再双层遍历 +isEqual,别强塞Set
Set 不会自动转换类型,"1" 和 1 被视为不同值。上线前务必检查数据契约,必要时统一类型再进 Set。

















