es-toolkit fp 模块 xorBy 函数详解:在 pipe 管道中按映射键计算数组对称差
2026/9/17 5:54:57 网站建设 项目流程

es-toolkit fp 模块 xorBy 函数详解:在 pipe 管道中按映射键计算数组对称差

【免费下载链接】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

xorBy是 es-toolkit 函数式编程(es-toolkit/fp)子模块中的一个数据后置(data-last)操作符,它基于自定义映射函数(mapper)产出的比较键,返回只出现在两个数组之一中的元素。本文围绕 fp/xorBy 参考文档 展开,结合 src/fp/array/xorBy.ts 及其底层数组实现,讲解它的使用方式、参数约定、内部原理,以及它与普通版xorBy的取舍,帮助你直接在pipe组合链中使用。

一句话理解 xorBy(fp 版)

普通数组版xorBy的调用形式是xorBy(arr1, arr2, mapper),一次接收三个参数;而 fp 版将参数顺序重排为「先传配置、后传数据」:xorBy(secondArray, mapper)返回一个新函数,这个函数再接收被 pipe 传入的数组。

import { pipe, xorBy } from 'es-toolkit/fp'; const result = pipe( [{ id: 1 }, { id: 2 }], xorBy([{ id: 2 }, { id: 3 }], item => item.id) ); // => [{ id: 1 }, { id: 3 }]

这段代码的含义是:以item.id作为比较键,{ id: 1 }只存在于第一个数组、{ id: 3 }只存在于第二个数组,因此两者被保留;{ id: 2 }在两个数组中都有映射键2,属于交集,被剔除。

使用方式与运行结果

xorBy会先对每个元素调用mapper得到比较键,然后只返回「映射键恰好只出现在其中一个数组」的元素。官方参考文档给出的完整示例如下:

import { pipe, xorBy } from 'es-toolkit/fp'; pipe( [{ id: 1 }, { id: 2 }], xorBy([{ id: 2 }, { id: 3 }], item => item.id) ); // => [{ id: 1 }, { id: 3 }]

参数说明

参数类型说明
secondArrayreadonly T[]与被 pipe 传入的数组进行比较的第二个数组
mapper(item: T) => U为每个元素返回比较键的函数

返回值

返回类型为(array: readonly T[]) => T[]的函数:它接收一个readonly T[],将其转换为「按映射键计算出的对称差」数组。这个返回值可以直接作为pipe的后续操作符使用,这正是 fp 子模块操作符的通用契约。

从 src/fp/array/xorBy.ts 可以看到,fp 版本身只是薄薄一层柯里化封装——它捕获secondArraymapper,返回一个闭包函数,真正计算时把三个参数原样转发给普通版:

export function xorBy<T, U>(secondArray: readonly T[], mapper: (item: T) => U): (array: readonly T[]) => T[] { return function (array: readonly T[]): T[] { return xorByToolkit(array, secondArray, mapper); }; }

fp 版与普通版如何选择

参考文档中的提示(info 框)给出了一条清晰的取舍准则:

  • 普通代码(非管道组合)中,优先使用普通版xorBy:即 reference/array/xorBy.md 中描述的xorBy(arr1, arr2, mapper),它一次调用即可完成计算,无需额外包装。
  • 当用pipe串联多个变换时,使用 fp 版:fp 版符合「数据最后传入」的约定,可以让pipe(array, xorBy(secondArray, mapper))mapfilteruniqBy等操作符自然衔接,避免层层嵌套。

普通版调用示例(src/array/xorBy.ts):

import { xorBy } from 'es-toolkit/array'; xorBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], item => item.id); // => [{ id: 1 }, { id: 3 }]

底层实现原理

虽然 fp 版直接委托给了普通版,但普通版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); }

计算流程分三步:

  1. 并集unionBy(arr1, arr2, mapper)先拼接两个数组,再按映射键去重,得到所有出现过的元素(见 src/array/unionBy.ts,内部调用uniqBy(arr1.concat(arr2), mapper))。
  2. 交集intersectionBy(arr1, arr2, mapper)从第一个数组中筛出映射键在第二个数组中也存在的元素(见 src/array/intersectionBy.ts,内部用Set记录第二个数组的映射键,并对已匹配的键执行delete保证去重)。
  3. 差集differenceBy(union, intersection, mapper)从并集中剔除交集,得到只出现一次的元素(见 src/array/differenceBy.ts,同样以Set存储待排除的映射键)。

因此对称差 = 并集 − 交集,这与「只出现在两个数组之一」的语义完全一致。三个底层函数都基于Set实现键值判等,平均复杂度接近线性,适合处理中等规模数组。

一个容易被忽略的行为:映射键相同视为同一个元素

参考文档和测试均强调一个语义细节:只要映射函数产出的键相同,元素就被视为同一份,即使原始对象本身不同。这一点在 fp 版源码 JSDoc(src/fp/array/xorBy.ts)与普通版文档中都有明确说明。

例如下面的调用会得到空数组,因为所有元素的映射键都被另一侧「覆盖」了:

import { xorBy } from 'es-toolkit/array'; // 以字符串长度作为比较键 xorBy(['apple', 'banana'], ['grape', 'cherry', 'apple'], str => str.length); // => [](两边的长度键 5、6 均重复出现)

这也意味着mapper的选择直接决定结果:键越“粗粒度”(如按n % 3、字符串长度),被合并、剔除的元素越多;键越“细粒度”(如对象唯一id),结果越接近元素级对比。

测试用例验证

仓库为 fp 版xorBy提供了最小但完整的测试用例(src/fp/array/xorBy.spec.ts),验证其在pipe中的行为:

import { describe, expect, it } from 'vitest'; import { xorBy } from './xorBy.ts'; import { pipe } from '../pipe.ts'; describe('xorBy', () => { it('works in a pipe', () => { expect( pipe( [{ id: 1 }, { id: 2 }], xorBy([{ id: 2 }, { id: 3 }], item => item.id) ) ).toEqual([{ id: 1 }, { id: 3 }]); }); });

测试通过toEqual断言输出数组[{ id: 1 }, { id: 3 }],与参考文档中的示例完全一致,可作为你本地验证实现行为的基准。

与 pipe 组合的实战要点

pipees-toolkit/fp的入口,负责从左到右把数据依次穿过每个操作符。fp 版xorBy之所以返回「接收数组的函数」,正是为了满足 pipe 对操作符「data-last」的约定,例如把去重、筛选与对称差组合成一条链:

import { pipe, xorBy, uniqBy } from 'es-toolkit/fp'; const result = pipe( [{ id: 1, tag: 'a' }, { id: 2, tag: 'b' }, { id: 2, tag: 'c' }], uniqBy(item => item.id), // 先按 id 去重 xorBy([{ id: 3 }, { id: 1 }], item => item.id) // 再与另一数组求对称差 ); // => [{ id: 2, tag: 'b' }, { id: 3 }]

值得说明的是,fp 版的xorBy属于非惰性(eager)函数:从源码看它没有附加lazy标记,因此它不会被 pipe 的惰性融合路径处理,而是按普通函数逐个执行(可参见 src/fp/pipe.ts 中chunkFunctions对函数分组、惰性组合的逻辑)。这意味着它会在自己这一环立即完成完整的并集/交集/差集计算并返回新数组,不影响其正确性,只是不具备mapfiltertake那类逐元素短路求值的优化。

小结

  • fp 版xorBy(secondArray, mapper)返回一个「数组 → 对称差数组」的函数,专为pipe数据流设计;
  • 它内部委托给普通版 xorBy,底层由unionByintersectionBydifferenceBy三个Set驱动的函数组合而成;
  • 比较键由mapper决定,映射键相同的元素视为同一份,选择键的粒度直接影响结果;
  • 普通(非管道)代码推荐直接使用 es-toolkit/array 的 xorBy,管道组合场景才使用 fp 版。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询