es-toolkit/fp 中的 differenceBy:函数式管道中的按映射键差集操作
【免费下载链接】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/fp是 es-toolkit 的函数式编程入口,它把differenceBy等数组操作改写为data-last(数据最后传入)的形态,使其可以直接嵌入pipe管道,用自上而下的顺序表达数据变换。本文将围绕 docs/fp/reference/differenceBy.md 展开,讲解 fp 版differenceBy的用法、参数语义、返回值,并结合仓库源码剖析其基于Set的去重实现与pipe中的惰性求值(lazy evaluation)机制,帮助你在实际项目中写出可读、高效且可提前终止的管道式差集代码。
一、fp 版 differenceBy 是什么
differenceBy(函数式变体)会创建一个"按映射键排除值"的函数,通常与pipe搭配使用:
const result = pipe(array, differenceBy(secondArray, mapper));它与 es-toolkit 原始版differenceBy的区别在于调用形态:
- 原始版(data-first):
differenceBy(firstArr, secondArr, mapper),三个参数一次性传入,返回结果数组; - fp 版(data-last):
differenceBy(secondArray, mapper),只传入"配置"(要排除的数组和映射函数),返回一个等待数据的函数(array) => T[],由pipe把数据喂给它。
正如文档中的提示所述:在普通代码中应优先使用原始版differenceBy;只有当你需要与pipe组合表达变换流水线时,才使用这个 fp 变体。
二、用法:按映射键计算差集
differenceBy会比较mapper返回的值:来自管道数组(piped array)的元素,只要其映射键没有出现在secondArray中,就会被保留。
import { differenceBy, pipe } from 'es-toolkit/fp'; pipe( [{ id: 1 }, { id: 2 }], differenceBy([2], value => (typeof value === 'number' ? value : value.id)) ); // => [{ id: 1 }]这个例子里,mapper对两种类型的元素做了归一化:数字2映射为自身2,对象{ id: 2 }映射为2。映射后两者的键相同,因此{ id: 2 }被排除,只留下{ id: 1 }。这正是"基于特定标准计算差集"的典型用法——两个数组的元素类型可以不同,只要映射后的键可比即可。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
secondArray | readonly U[] | 包含应被排除元素(按映射键)的数组 |
mapper | (item: T \| U) => unknown | 返回比较键的函数,同时作用于两个数组的元素 |
返回值
(array: readonly T[]) => T[]—— 一个函数,将readonly T[]映射为"键未出现在secondArray中"的元素数组。
三、源码剖析:data-last 签名与 Set 映射
fp 版differenceBy的完整实现在 src/fp/array/differenceBy.ts,核心签名如下:
export function differenceBy<T, U>( secondArray: readonly U[], mapper: (value: T | U) => unknown ): (array: readonly T[]) => T[] { const mappedSecondSet = new Set(secondArray.map(item => mapper(item))); function differenceByEager(array: readonly T[]): T[] { return differenceByToolkit(array, secondArray, mapper); } const differenceByLazy = createLazyFunction<T, T>((value, _index, emit) => { if (!mappedSecondSet.has(mapper(value))) { emit(value); } }); return combineEagerAndLazyFunctions(differenceByEager, differenceByLazy); }这里可以观察到三个关键设计:
闭包捕获配置,数据后置:调用
differenceBy(secondArray, mapper)时立即用new Set(secondArray.map(item => mapper(item)))预计算"排除键集合"(详见 src/fp/array/differenceBy.ts),返回的函数只负责接收数组并做过滤判断。由于Set查找是 O(1),整个过滤过程对每个元素只需一次映射加一次哈希查找。eager 实现直接复用原始版:
differenceByEager内部调用differenceByToolkit(array, secondArray, mapper),即 src/array/differenceBy.ts 中的原始>const groups = chunkFunctions(functions); if (group.lazy && isIterable(output) && (group.shortCircuit || !Array.isArray(output))) { output = lazyPipe(output, group); }lazyPipe(src/fp/pipe.ts)从最后一个函数开始倒序组合各 lazy 变换,然后用单层循环逐元素驱动整条管道,不再为每一步构建中间数组。当尾部存在take这类shortCircuit函数时,某个emit返回false会立刻中断驱动循环(src/fp/pipe.ts),剩余输入永远不会被访问。对
differenceBy而言,这意味着你可以写出这样的高效管道:import { pipe, differenceBy, take } from 'es-toolkit/fp'; // 只要收集到 2 个结果就停止遍历,大数组的大部分元素根本不会被处理 pipe( hugeArray, differenceBy([blockedKey1, blockedKey2], item => item.id), take(2) );由代码结构可以推断:
differenceBy的 lazy 形态与take等短路函数相邻时,pipe会融合它们并提前终止;而单独调用或与普通函数混排时,则走differenceByEager的原生数组路径,性能与filter相当,没有任何额外开销。五、测试验证与边界行为
仓库中的测试 src/array/differenceBy.spec.ts 验证了核心语义:
- 基础映射差集:
differenceBy([1.2, 2.3, 3.4], [1.2], Math.floor)返回[2.3, 3.4]——1.2与1.2经Math.floor都映射为1,故被排除;空数组输入返回空数组。 - 异构类型比较:
CSV[]与JSON[]两种不同结构(仅id相同)的数组,通过value => value.id映射后正确排除id相同的元素。
这两个用例说明了
differenceBy的两个实用边界:- 比较键是映射结果而非元素本身,因此"1.2 与 1.2 按
Math.floor视为相同"这类场景必须用differenceBy而非difference; - 两个数组类型可以不同(
T ≠ U),只要mapper能把两边归一化到同一键空间。
六、何时用 fp 版 differenceBy
结合 docs/fp/intro.md 的定位,建议按如下原则取舍:
- 需要表达自上而下的变换流水线(如
pipe(array, differenceBy(blocked, k => k.id), take(n)))时,使用 fp 版,可读性与融合效率兼得; - 只需要一次性计算两个数组的差集时,直接使用
differenceBy(data-first 形态),调用更简洁; - 从 lodash 迁移、需要保持 lodash 调用风格的项目,可参考
es-toolkit/compat入口。
总结
fp 版
differenceBy是 es-toolkit/fp 中"配置先行、数据后至"设计的典型代表:它预计算排除键集合、复用原始版核心实现,并通过combineEagerAndLazyFunctions同时具备 eager 与 lazy 两种执行形态。放入pipe后,它可以与相邻惰性函数融合为单次遍历并支持提前终止,让你以自上而下的可读代码获得接近手写循环的效率。理解其实现(src/fp/array/differenceBy.ts、src/fp/_internal/lazy.ts、src/fp/pipe.ts)后,你可以更有把握地在实际项目中编排差集过滤管道。【免费下载链接】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),仅供参考