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 的 Lodash 兼容版xorBy展开,讲解如何基于自定义变换规则(函数、属性名、部分对象)计算多个数组的对称差集,并结合仓库源码剖析其内部实现、与原生版xorBy的差异以及性能取舍。读完本文,你将掌握xorBy的完整调用方式、iteratee的四种形态,以及何时应该改用更快的现代版 API。
一、xorBy 是什么:兼容层中的对称差集工具
xorBy是 es-toolkit 的 Lodash 兼容模块(es-toolkit/compat)中提供的数组工具函数。它的作用是对多个数组做**对称差集(symmetric difference)**计算,但比较的基准不是元素本身,而是每个元素经过变换函数(iteratee)处理后的结果。
所谓对称差集,是指「只存在于其中一个集合、但不同时出现在多个集合中」的元素集合。与xor相比,xorBy多接受一个变换函数,允许你按对象的某个属性、字符串长度、取整结果等任意规则来判定元素是否重复,因此在处理对象数组时尤为实用。
需要注意的是,在 es-toolkit 的兼容文档 中,官方明确给出警告:兼容版xorBy因需要处理null/undefined、复杂的去重计算逻辑,运行速度较慢,推荐优先使用更快的现代版 xorBy(array 模块)。兼容版的定位是为 Lodash 代码平滑迁移提供行为一致性,而不是追求极致性能。
二、基本用法与完整示例
xorBy的调用签名如下:
const result = xorBy(...arrays, iteratee);多个数组依次传入,最后一个参数是变换函数(iteratee)。函数会对每个数组的每个元素应用该变换,然后按变换后的结果计算对称差集:变换结果恰好只出现在其中一个数组中的元素会被保留在结果中。
从es-toolkit/compat导入:
import { xorBy } from 'es-toolkit/compat';场景一:按 Math.floor 取整结果比较数值
// 以 Math.floor 的结果为基准计算对称差集 xorBy([2.1, 1.2], [4.3, 2.4], Math.floor); // 返回: [1.2, 4.3]2.1与2.4取整后都是2,属于重复项被剔除;1.2(floor 为1)和4.3(floor 为4)的变换结果只出现一次,因此被保留。
场景二:按对象属性名比较
// 以对象的 x 属性为基准计算对称差集 xorBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x'); // 返回: [{ x: 2 }]这里iteratee直接传入了字符串'x',es-toolkit 会把它解析为「取对象的x属性」函数。{ x: 1 }在两个数组中同时出现,被剔除;{ x: 2 }只出现一次,被保留。
场景三:传函数作为变换规则
const users1 = [{ name: 'John', age: 30 }]; const users2 = [ { name: 'Jane', age: 25 }, { name: 'John', age: 30 }, ]; xorBy(users1, users2, user => user.name); // 返回: [{ name: 'Jane', age: 25 }]John同时存在于两个数组,被剔除;Jane只出现一次,保留完整对象。
场景四:三个数组的对称差集
xorBy([1.2, 2.3], [3.4, 4.5], [5.6, 6.7], Math.floor); // 返回: [1.2, 2.3, 3.4, 4.5, 5.6, 6.7]三个数组各元素的取整结果(1、2、3、4、5、6)互不重复,因此全部保留。
场景五:null / undefined 会被忽略
xorBy([2.1, 1.2], null, [4.3, 2.4], Math.floor); // 返回: [1.2, 4.3]null和undefined参数会被静默忽略,不会参与计算,也不会抛错。这在从 Lodash 迁移、参数可能动态拼接的场景下很安全。
参数与返回值
参数
...arrays(Array<ArrayLike<T> | null | undefined | ValueIteratee<T>>):参与对称差集计算的多个数组,以及末尾的变换函数。数组可以是类数组对象(ArrayLike)、null或undefined;变换函数可以是函数、属性名、部分对象等(详见下文 iteratee 详解)。
返回值
- (
T[]):以变换函数的结果为基准,只出现在其中一个数组中的元素组成的新数组。
三、iteratee 的四种形态:从源码看参数解析
兼容版xorBy的关键设计在于对iteratee的灵活解析。查看其实现 src/compat/array/xorBy.ts,可以看到处理逻辑:
export function xorBy<T>(...values: Array<ArrayLike<T> | null | undefined | ValueIteratee<T>>): T[] { const lastValue = last(values); let mapper = identity; if (!isArrayLikeObject(lastValue) && lastValue != null) { mapper = iteratee(lastValue); values = values.slice(0, -1); } // ... }实现首先取出最后一个参数lastValue,如果它不是类数组对象(isArrayLikeObject为 false)且不为null/undefined,就把它当作 iteratee 交给iteratee工厂函数 转换成统一格式的映射函数,同时将其从数组列表中剔除。也就是说,只要最后一个参数是数组或类数组,就不会被误当成 iteratee,这也解释了为何xorBy([1, 2], [2, 3])不带 iteratee 也能正常工作(此时 mapper 退化为恒等函数identity)。
iteratee工厂函数支持四种输入形态(见 src/compat/util/iteratee.ts):
| 传入形态 | 解析结果 | 示例 |
|---|---|---|
| 函数 | 原样返回该函数 | iteratee(user => user.name) |
| 属性名字符串/数字/symbol | 返回取值函数property(value) | iteratee('x')等价于obj => obj.x |
二元数组[key, value] | 返回匹配属性值的函数matchesProperty | iteratee(['x', 1])判断obj.x === 1 |
| 部分对象 | 返回深度匹配函数matches | iteratee({ x: 1 })判断对象是否匹配该子集 |
null/undefined/缺省 | 返回恒等函数identity | 元素原样参与比较 |
这四种形态意味着兼容版xorBy可以完全复刻 Lodash 的调用习惯:既可以用Math.floor这样的函数,也可以用'x'这样的属性名,甚至可以传入{ status: 'active' }这样的部分对象做结构化比较。
四、底层实现:并集、交集与差集的组合运算
兼容版的实现思路
从 src/compat/array/xorBy.ts 的核心实现可以看到,对称差集是通过「并集 - 交集」的组合运算得出的:
const arrays = values.filter(isArrayLikeObject) as [any]; if (arrays.length < 2) { return uniq(arrays[0]); } const union = unionBy(...arrays, mapper); const intersections = windowed(arrays, 2).map(([arr1, arr2]) => intersectionBy(arr1, arr2, mapper)) as [any]; return differenceBy(union, unionBy(...intersections, mapper), mapper) as T[];具体步骤:
- 用
filter(isArrayLikeObject)剔除所有非数组参数(null、undefined、数字等),仅保留真正的数组/类数组参与计算; - 如果有效数组少于 2 个,直接返回
uniq去重后的结果(与 Lodash 行为一致,单数组只做去重); - 计算所有数组的并集(
unionBy); - 用
windowed(arrays, 2)对数组做滑动窗口分组,对每两个相邻数组计算交集(intersectionBy),再把所有交集合并(unionBy)得到「重复元素集合」; - 最后用
differenceBy(union, 重复集合, mapper)从并集中剔除所有重复元素,得到对称差集。
这种「并集减去重复项」的通用算法可以正确处理任意数量的数组,代价是多次全量遍历与去重,这正是官方文档警告「复杂重复计算逻辑导致较慢」的根源。
现代版的简洁对照
作为对比,现代版 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); }由于现代版不做null/undefined容忍、不支持属性名/部分对象等 iteratee 形态、也不需要处理任意数量数组,其内部循环与中间数组更少,因此更快、包体积更小。这也是文档推荐优先使用es-toolkit/array版xorBy的原因。
两者的性能差异在仓库的基准测试 benchmarks/performance/xorBy.bench.ts 中有直接对比:测试同时运行es-toolkit/xorBy、es-toolkit/compat/xorBy与lodash/xorBy,并且包含一个各 10000 个对象(id从0到9999、5000到14999)的大数组场景。如需自行复现,可在仓库根目录运行基准测试命令。
五、边界行为与测试验证
兼容版xorBy的边界行为在 src/compat/array/xorBy.spec.ts 中有完整覆盖,值得关注的语义包括:
1. 单数组返回去重结果
xorBy([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]); // => [1, 4] xorBy([1, 1]); // => [1](单数组只去重) xorBy([1]); // 返回新数组,与原数组引用不同2. 非数组参数被忽略
const array = [1, 2]; xorBy(array, 3, { 0: 1 }, null); // => array(数字、普通对象、null 均被忽略) xorBy(null, array, null, [2, 3]); // => [1, 3]注意普通对象{ 0: 1 }会被忽略,但类数组对象(如函数的arguments对象)会被当作有效数组参与计算——这正是isArrayLikeObject检查的意义。
3. 单数组时 iteratee 被忽略(与 Lodash 一致)
xorBy([2.1, 2.3], Math.floor); // => [2.1, 2.3](不应用 iteratee) xorBy(Math.floor); // => [](没有数组时返回空数组) xorBy([NaN, NaN], Math.floor); // => [NaN] xorBy([-0], Math.floor); // => [0]这是一个容易踩坑的细节:当只传一个数组时,末尾参数即使看起来像函数,也不会被当作 iteratee 处理。测试中还验证了-0会被规范化为0、NaN按自身去重等一致性行为。
4. 元素去重语义
xorBy([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]); // => [1, 4]数组内部的重复元素先被合并,再参与对称差集计算,结果中不会出现重复项。
六、应用场景与迁移建议
综合来看,兼容版xorBy的典型使用场景包括:
- Lodash 迁移:已有代码使用
lodash.xorBy,希望无痛切换到 es-toolkit。由于兼容层刻意保持 Lodash 的调用签名与边界行为(支持任意数量数组、null容忍、字符串属性名 iteratee),通常只需把 import 路径从lodash改为es-toolkit/compat即可。 - 对象数组按属性比较:需要按
id、name等属性判定重复,但又希望返回完整的原始对象(而不是属性值本身)。 - 多数组场景:三个以上数组需要计算「全局只出现一次」的元素,且对
null参数有容错需求。
而如果你的代码是全新编写、只需要在两个数组间按函数规则计算对称差集,官方建议直接使用 es-toolkit/array 的 xorBy,它在性能与包体积上更优:
import { xorBy } from 'es-toolkit/array'; xorBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], obj => obj.id); // 返回: [{ id: 1 }, { id: 3 }]总结
xorBy是 es-toolkit 兼容层中用于按自定义规则计算多数组对称差集的标准工具。它通过iteratee工厂把函数、属性名、属性值对、部分对象统一转换为比较基准,通过「并集 - 交集」的组合算法处理任意数量的数组,并对null/undefined、非数组参数、单数组去重等边界行为提供了与 Lodash 一致的处理。理解其实现原理与边界语义,能帮助你在迁移 Lodash 代码时做出正确的取舍:追求行为一致用es-toolkit/compat的xorBy,追求性能与体积则选用es-toolkit/array的现代版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
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考