ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

es-toolkit/fp 中的 differenceWith:用自定义相等函数在 pipe 管道中实现差集过滤

es-toolkit/fp 中的 differenceWith:用自定义相等函数在 pipe 管道中实现差集过滤 es-toolkit/fp 中的 differenceWith用自定义相等函数在 pipe 管道中实现差集过滤【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit本文聚焦 es-toolkit 函数式编程入口es-toolkit/fp提供的differenceWith。它是一个数据后置data-last的管道算子先用secondArray与areItemsEqual完成配置返回一个等待数组输入的函数再与pipe组合在自左向右的数据流中按自定义相等规则剔除匹配项。读完本文你将掌握differenceWith的参数契约、与普通版differenceWith的选用准则、它在pipe内触发惰性求值lazy evaluation与提前终止的底层原理以及如何通过测试用例验证其行为。概览为 pipe 而生的差集算子differenceWith创建一个函数该函数使用自定义相等函数从管道传入的数组中排除与配置值相等的元素。它专用于函数式编程风格典型用法是与pipe串联const result pipe(array, differenceWith(secondArray, areItemsEqual));在es-toolkit/fp的定位中每个 fp 函数都是先传配置、后传数据的形态differenceWith(secondArray, areItemsEqual)先完成配置并返回(array) T[]pipe再把数据逐个喂给这些算子见 fp 入口文档。选用建议在不使用pipe拼接管道的普通代码中推荐直接使用 es-toolkit 的普通版differenceWith。只有当你要通过pipe串联多个变换时才使用这里的 fp 版本。用法与语义differenceWith仅当areItemsEqual对secondArray中的每一个值都返回false时才保留被管道传入数组中的对应值。换句话说它返回的是与比较数组没有任何元素相等的子集。import { differenceWith, pipe } from es-toolkit/fp; pipe( [{ id: 1 }, { id: 2 }], differenceWith([{ id: 2 }], (a, b) a.id b.id) ); // [{ id: 1 }]上例中{ id: 2 }与比较数组中的{ id: 2 }按id相等因此被剔除最终结果只剩[{ id: 1 }]。参数Parameters参数类型说明secondArrayreadonly U[]用于与管道输入逐项比较、从而决定排除哪些值的数组areItemsEqual(item: T, other: U) boolean判定两个值是否相等的自定义比较函数返回值Returns(array: readonly T[] T[])一个将readonly T[]映射为未被比较函数匹配到的值组成的新数组的函数。从普通版到 fp 版实现与复用fp 版differenceWith并没有重新发明算法而是复用了 es-toolkit 普通版differenceWith的实现。源码见 src/fp/array/differenceWith.tsexport function differenceWithT, U( secondArray: readonly U[], areItemsEqual: (item: T, other: U) boolean ): (array: readonly T[]) T[] { function differenceWithEager(array: readonly T[]): T[] { return differenceWithToolkit(array, secondArray, areItemsEqual); } const differenceWithLazy createLazyFunctionT, T((value, _index, emit) { if (secondArray.every(other !areItemsEqual(value, other))) { emit(value); } }); return combineEagerAndLazyFunctions(differenceWithEager, differenceWithLazy); }这段代码揭示了三个关键点eager 路径复用普通实现differenceWithEager内部直接调用src/array/differenceWith.ts导出的differenceWith签名变化只是把(firstArr, secondArr, areItemsEqual)柯里化成了(secondArray, areItemsEqual)(array)。lazy 路径按元素推送differenceWithLazy用createLazyFunction构造逐元素变换每个元素只有通过secondArray.every(other !areItemsEqual(value, other))的检查才会被emit推送到下游。两者被combineEagerAndLazyFunctions合并返回的函数在直接调用时行为与 eager 完全一致放进pipe后附加的lazy变换让pipe可以将其与相邻惰性函数熔合为单趟遍历。普通版differenceWith的算法本身非常直接src/array/differenceWith.tsexport function differenceWithT, U( firstArr: readonly T[], secondArr: readonly U[], areItemsEqual: (x: T, y: U) boolean ): T[] { return firstArr.filter(firstItem { return secondArr.every(secondItem { return !areItemsEqual(firstItem, secondItem); }); }); }它等价于对firstArr中的每个元素firstItem仅当secondArr中不存在任何一个使areItemsEqual(firstItem, secondItem)为真的secondItem时保留。从源码结构看其时间复杂度为 O(n×m)n 为第一个数组长度m 为第二个数组长度比较函数每次被调用的次数等于两数组长度的乘积上界。惰性求值为什么在 pipe 里更快es-toolkit/fp的核心卖点是管道熔合pipeline fusion。当pipe中连续出现可惰性函数map、filter、take、differenceWith等时pipe会把它们熔合成一趟遍历每个元素一次性穿过所有步骤而不是每步都生成中间数组一旦末尾的take(n)收集够结果整趟遍历立即停止剩余输入不再被访问。完整机制见 src/fp/pipe.ts 与惰性原语 src/fp/_internal/lazy.ts。createLazyFunction实现的是一种推送式push管道每个函数接收下游的emit即 sink再把值推入其中take这类可提前终止的函数通过返回false立即停住驱动循环无需生成器挂起/恢复也没有逐元素的迭代器对象开销。测试如何验证提前终止differenceWith的惰性行为有专门的测试用例见 src/fp/array/differenceWith.spec.tsit(supports lazy evaluation with a short-circuiting operator, () { const spy vi.fn((item: { id: number }) item); expect( pipe( [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }], map(spy), differenceWith([1], (item, id) item.id id), take(2) ) ).toEqual([{ id: 2 }, { id: 3 }]); expect(spy).toHaveBeenCalledTimes(3); });这个测试用vi.fn间谍函数记录了map的回调调用次数输入有 4 个元素但spy只被调用了3 次。原因正是熔合与提前终止——take(2)在收集到 2 个结果后立即中断整趟遍历第 4 个元素{ id: 4 }从未被访问。若按 eager 逐数组执行map必然对全部 4 个元素各调用一次。与 difference / differenceBy 的对比es-toolkit/fp的数组模块同时提供了三个差集算子见 src/fp/array/index.ts 的导出difference(secondArray)基于 Set 与 SameValueZero 相等语义做差集。fp 实现src/fp/array/difference.ts在配置阶段就构建new Set(secondArray)惰性变换用!secondSet.has(value)判断查找为 O(1)适合处理原始值数组。differenceBy(secondArray, getDiffValue)先用映射函数把两数组元素变换成可比较键再做差集适合按某字段去重的场景。differenceWith(secondArray, areItemsEqual)自由度最高比较逻辑完全由你提供的areItemsEqual决定适合对象数组按业务规则如按id、按name忽略年龄差异比较也可比较类型不同的数组。三者共享同一套eager 复用普通实现 lazy 逐元素变换 combineEagerAndLazyFunctions合并的结构因此在pipe中都能参与惰性熔合。实战对象数组按业务键过滤结合普通版文档docs/reference/array/differenceWith.md中的示例我们可以在pipe中实现忽略次要字段差异的过滤。例如用户名单中 Alice 的年龄从 30 变为 31但我们仍认为她是同一用户需要把users2中出现的用户从users1中剔除import { differenceWith, pipe } from es-toolkit/fp; const users1 [ { name: Alice, age: 30 }, { name: Bob, age: 25 }, { name: Charlie, age: 35 }, ]; const users2 [ { name: Alice, age: 31 }, // 年龄不同但仍是同一用户 { name: David, age: 25 }, ]; pipe( users1, differenceWith(users2, (a, b) a.name b.name) ); // [{ name: Bob, age: 25 }, { name: Charlie, age: 35 }]areItemsEqual只比较name因此Alice即使 age 不同被排除David不在users1中所以不影响结果。当这个differenceWith前后还串联了map、filter、take等算子时整条管道会以单趟、可提前终止的方式执行。小结differenceWith(secondArray, areItemsEqual)返回一个数据后置函数仅在areItemsEqual对secondArray全部元素都为false时保留输入元素它是 fp 版柯里化封装eager 路径直接复用普通版differenceWithlazy 路径用createLazyFunction实现逐元素推送在pipe中它与相邻惰性函数熔合为单趟遍历配合take等短路算子可实现提前终止测试用 spy 断言map仅调用 3 次即验证了这一点普通代码请用es-toolkit/array的普通版需要按自定义规则对象字段、跨类型比较做差集时fp 版differenceWith是pipe管道中的正确选择。【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表