es-toolkit 的 xorBy:基于映射函数的两数组对称差集详解
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
本指南围绕 es-toolkit 数组工具库中的xorBy函数展开,讲解它如何在"先通过映射函数变换、再比较"的语义下计算两个数组的对称差集(symmetric difference),并结合 src/array/xorBy.ts 的源码实现与 src/array/xorBy.spec.ts 的测试用例,说明其使用场景、参数约束、边界行为与底层原理。读完本文,你将能够熟练使用xorBy处理对象数组、按任意维度去重比较,并理解它与xor、unionBy、intersectionBy、differenceBy等函数的组合关系。
xorBy 是什么:带映射基准的对称差集
在集合论中,两个集合的对称差集是指"仅属于其中一个集合、但不同时属于两个集合"的元素集合。es-toolkit 的xorBy就是这一概念的数组实现,特殊之处在于:它不直接比较元素本身,而是先通过用户提供的mapper函数把每个元素变换成比较基准值,再基于这些基准值计算对称差集。
const result = xorBy(arr1, arr2, mapper);这在处理对象数组时尤为实用——两个数组中的对象引用各不相同,直接比较毫无意义,但提取出id、length等字段后即可准确判断"哪些记录只存在于一边"。
基本用法
import { xorBy } from 'es-toolkit/array'; // 按对象的 id 求对称差集 xorBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], obj => obj.id); // Returns: [{ id: 1 }, { id: 3 }] // 按字符串长度求对称差集 xorBy(['apple', 'banana'], ['grape', 'cherry', 'apple'], str => str.length); // Returns: [] (所有长度都出现重复)第一个例子中,id为 2 的对象在两个数组中同时出现,属于交集,被剔除;id为 1、3 的对象各只出现在一个数组中,被保留。第二个例子中,四个字符串的长度分别为 5、6、5、6,全部在两侧重复,因此对称差集为空。
映射结果相同的元素被视作同一个
xorBy的一个关键语义是:凡是映射函数输出相同值的元素,在比较时都视作同一个元素,因此无需关心元素原始值是否相等。
import { xorBy } from 'es-toolkit/array'; xorBy([1, 2, 3, 4], [3, 4, 5, 6], n => n % 3); // Returns: [] (所有余数均出现重复)这里映射结果为n % 3:第一个数组的余数为 1、2、0、1,第二个数组的余数为 0、1、2、0,集合 {0, 1, 2} 在两数组中完全重合,故结果为空数组。可见xorBy关注的是"映射后的取值域"是否重合,而非元素本身。
参数与返回值
xorBy的完整签名(见 src/array/xorBy.ts):
export function xorBy<T, U>(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[]arr1(readonly T[]):参与比较的第一个数组,只读,函数不会修改它。arr2(readonly T[]):参与比较的第二个数组,同样只读。mapper((item: T) => U):把每个元素转换为比较基准值的函数,其返回类型U即比较键的类型。- 返回值(
T[]):一个新数组,包含基于映射结果计算出的对称差集元素;原数组不被改动。
由于泛型T、U的存在,mapper可以返回任意可比较类型——数字、字符串、布尔值均可,Set/Map结构对它们使用 SameValueZero 语义判等。
源码原理:union、intersection、difference 的三段式组合
xorBy的实现极为精简,其核心思想可以概括为一条公式:
对称差集 = 并集 − 交集
// 源码:src/array/xorBy.ts export function xorBy<T, U>(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[] { const union = unionBy(arr1, arr2, mapper); const intersection = intersectionBy(arr1, arr2, mapper); return differenceBy(union, intersection, mapper); }三个步骤分别委托给三个同族函数:
unionBy(arr1, arr2, mapper):先合并两个数组,再按映射值去重,得到"映射基准下的并集"。src/array/unionBy.ts 的实现是uniqBy(arr1.concat(arr2), mapper),而uniqBy(见 src/array/uniqBy.ts)使用Map记录映射值并保留首次出现的元素,保证去重后仍按原顺序排列。intersectionBy(arr1, arr2, mapper):找出"映射基准下同时存在于两个数组的元素"。src/array/intersectionBy.ts 先把第二个数组整体映射后放入Set,再遍历第一个数组,命中即保留并从 Set 中删除该键,因此重复元素只计一次。differenceBy(union, intersection, mapper):从并集中剔除交集,src/array/differenceBy.ts 同样是"先映射成 Set、再过滤"的写法,最终留下的就是只属于某一侧的对称差集元素。
这一三段式设计与无映射版本xor完全同构——src/array/xor.ts 的实现是difference(union(arr1, arr2), intersection(arr1, arr2))。可以推断,xorBy就是把xor的union/intersection/difference全部替换为带mapper的*By变体,从而把"按值比较"升级为"按映射基准比较"。
边界行为与测试佐证
src/array/xorBy.spec.ts 使用 Vitest 覆盖了多种边界场景,可作为理解函数行为的权威参考:
| 输入 | mapper | 期望输出 | 说明 |
|---|---|---|---|
[1,2,3,4]/[3,4,5,6] | 恒等函数 | [1,2,5,6] | 常规对称差集 |
[{id:1},{id:2}]/[{id:2},{id:3}] | obj => obj.id | [{id:1},{id:3}] | 对象按 id 比较 |
[1,2,3]/[4,5,6] | 恒等函数 | [1,2,3,4,5,6] | 无交集时退化为并集 |
[1,2,3]/[1,2,3] | 恒等函数 | [] | 完全重合时为空集 |
[]/[1,2,3] | 恒等函数 | [1,2,3] | 空数组与任意数组的对称差集即对方 |
[1,2,3]/[] | 恒等函数 | [1,2,3] | 同上,方向无关 |
这些用例同时验证了对称差集的交换律(xorBy(a, b)与xorBy(b, a)结果一致)以及"空数组参与时返回另一数组内容"的退化行为。其中对象按id去重的用例直接对应文档示例,恒等映射用例则说明xorBy(arr1, arr2, x => x)与xor(arr1, arr2)在数值数组上等价。
与同族函数的对比与选型
xorBy位于 es-toolkit 数组工具家族的"集合运算"梯队中,与其关系最密切的是:
xor:无映射版本,直接比较元素值,适合原始类型数组;unionBy/intersectionBy/differenceBy:分别计算并集、交集、差集(均为非对称),xorBy恰是它们的组合;xorWith(另一变体):支持自定义比较器而非映射函数,适合需要两两比较的复杂场景。
选型建议:当比较基准是"由元素派生的某个键"(如对象属性、取模结果、字符串长度)时选xorBy;当只需按元素本身判等时选xor即可获得更少的一次映射开销。
使用注意事项
- 映射函数应保持纯函数性:
mapper会在unionBy、intersectionBy、differenceBy三个阶段被多次调用,若依赖外部可变状态或产生副作用,可能得到不一致结果。 - 比较基于映射值而非原值:两个原始值不同、但映射结果相同的元素会被当作重复项处理,这与文档"映射函数结果相同的元素视为一个"的语义一致。
- 不会修改原数组:所有中间结果均为新数组,
arr1、arr2始终保持只读,可放心用于函数式编程流水线。
小结
xorBy用最简洁的三行组合实现了"基于映射基准的对称差集",其unionBy − intersectionBy的架构既保证了与无映射版本xor语义的一致性,又通过Map/Set保持了 O(n) 级别的查找效率。无论是按对象id同步数据、按字符串长度归类去重,还是按任意维度求"只属于一边"的元素,它都是 es-toolkit 中开箱即用的标准答案。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考