尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

es-toolkit 函数式编程 uniqWith 详解:在 pipe 流水线中用自定义相等函数高效去重

es-toolkit 函数式编程 uniqWith 详解:在 pipe 流水线中用自定义相等函数高效去重 es-toolkit 函数式编程 uniqWith 详解在 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 的fp函数式编程子模块中的uniqWith为核心讲解如何用它配合pipe完成基于自定义相等函数的去重。读完本文你将掌握uniqWith的>const result pipe(array, uniqWith(areItemsEqual));二、API 签名与类型fp版uniqWith定义于 src/fp/array/uniqWith.ts签名如下export function uniqWithT( areItemsEqual: (item: T, other: T) boolean ): (array: readonly T[]) T[]项目说明参数areItemsEqual(item: T, other: T) boolean用于判定两个值是否相等的函数。返回true表示视为重复false表示视为不同。返回值(array: readonly T[]) T[]一个把readonly T[]按自定义相等基准去重后映射为新数组的函数。对比普通版 uniqWith 的uniqWith(arr, areItemsEqual)fp版只是把参数顺序反转、将数组参数延迟到最后一步这正是>import { pipe, uniqWith } from es-toolkit/fp; pipe( [{ id: 1 }, { id: 1 }, { id: 2 }], uniqWith((a, b) a.id b.id) ); // [{ id: 1 }, { id: 2 }]由于uniqWith((a, b) a.id b.id)返回的是接收数组的函数它可以直接放在pipe的任意位置与map、filter、take等变换自由组合import { filter, map, pipe, take, uniqWith } from es-toolkit/fp; pipe( [ { id: 1, score: 60 }, { id: 1, score: 60 }, { id: 2, score: 85 }, { id: 3, score: 40 }, ], uniqWith((a, b) a.id b.id), map(item item.score), filter(score score 60), take(2) ); // [60, 85]比较函数并不局限于字段相等这种朴素场景。参考普通版文档 docs/reference/array/uniqWith.md 与源码注释中的示例可以构造各种自定义相等语义// 差值小于 1 视为相等 const numbers [1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19]; pipe( numbers, uniqWith((a, b) Math.abs(a - b) 1) ); // [1.2, 3.2, 5.7, 7.19] // 多个字段同时相等才视为重复 const products [ { name: iPhone, brand: Apple, price: 1000 }, { name: iPhone, brand: Apple, price: 1100 }, // name 与 brand 相同视为重复 { name: Pixel, brand: Google, price: 800 }, ]; pipe( products, uniqWith((a, b) a.name b.name a.brand b.brand) ); // 保留第一项 iPhone 与 Pixel四、源码级解析eager 与 lazy 的双轨实现fp版uniqWith的实现非常有代表性它通过combineEagerAndLazyFunctions把一个eager即时实现和一个lazy惰性变换打包进同一个函数src/fp/array/uniqWith.tsexport function uniqWithT(areItemsEqual: (item: T, other: T) boolean): (array: readonly T[]) T[] { function uniqWithEager(array: readonly T[]): T[] { return uniqWithToolkit(array, areItemsEqual); } const uniqWithLazy (emit: SinkT): SinkT { const seen: T[] []; return (value: T): boolean { if (seen.some(item areItemsEqual(item, value))) { return true; } seen.push(value); return emit(value); }; }; return combineEagerAndLazyFunctions(uniqWithEager, uniqWithLazy); }这里有两个关键细节eager 路径直接复用普通版实现。uniqWithEager委托给 src/array/uniqWith.ts后者用双层循环实现对每个新元素用result.every(v !areItemsEqual(v, item))与已保留的所有元素逐一比较不重复才 push 进结果数组。可以推断该算法最坏情况下的时间复杂度为 O(n²)在数据量极大时需要注意。lazy 路径维护一个seen缓存数组。对于流经的每个元素先检查它是否与seen中任一已保留元素相等相等则直接返回true表示继续推送但本元素丢弃不相等则加入seen并调用emit(value)把值传递给下一级。五、惰性求值原理pipe 如何融合 uniqWithcombineEagerAndLazyFunctions来自 src/fp/_internal/lazy.ts它做的只是Object.assign(eager, { lazy, shortCircuit })——把 lazy 变换以元数据形式挂到函数对象上。pipe在运行时读取这些元数据来决定执行策略src/fp/pipe.ts先用chunkFunctions把连续出现的惰性函数带lazy元数据切成一个个惰性组当一组函数全部可惰性、且输入是可迭代对象、并且组内有shortCircuit函数或输入不是数组时走lazyPipe融合路径否则逐个函数按普通方式依次应用。lazyPipesrc/fp/pipe.ts采用push式管道从最后一个函数开始把每个函数的 lazy 变换反向包成一个 sink最终形成一个从源头接收元素、向下游逐级 emit 的链条然后用一个循环把输入数组逐元素推进去。uniqWith的 lazy 变换就处在这个链条的某一环上游推来的每个元素先经过它内部的seen去重再把不重复的值 emit 给下游。这种设计带来两个实际收益无需中间数组map → uniqWith → filter这类连续惰性变换会融合为单趟遍历而不是每步生成一个新数组支持提前终止当组内存在take这类shortCircuit函数时它返回false会立刻中断驱动循环uniqWith之前的所有函数都不会再处理剩余输入。下面的测试用例src/fp/array/uniqWith.spec.ts用 spy 精确验证了这一点it(supports lazy evaluation with a short-circuiting operator, () { const spy vi.fn((item: { id: number }) item); expect( pipe( [{ id: 1 }, { id: 1 }, { id: 2 }, { id: 3 }], map(spy), uniqWith((a, b) a.id b.id), take(2) ) ).toEqual([{ id: 1 }, { id: 2 }]); expect(spy).toHaveBeenCalledTimes(3); // 第 4 个元素从未被 map 处理 });输入有 4 个元素但map只被调用了 3 次——因为uniqWith去重后只剩{id:1}和{id:2}两个值take(2)在拿到第二个值后立即短路{ id: 3 }甚至没有被map访问。这是惰性 短路融合的直接证据。六、行为要点与使用建议综合文档与源码使用fp版uniqWith时有几点值得注意保留首次出现的元素原文档与源码注释都确认每个相等组中第一个值被保留。这保证了结果顺序与输入顺序一致。比较函数应具备一致性areItemsEqual在 eager 路径中被反复调用每个新元素 vs 已保留元素其返回值应当稳定且对称否则去重结果会依赖遍历顺序。eager 与 lazy 行为等价直接调用uniqWith(areItemsEqual)(array)不走 pipe时走的是 eager 路径结果与普通版完全一致只有放进pipe并满足融合条件时才会启用 lazy 路径。与兄弟函数的取舍如果只是想按某个键去重uniqBy更合适如果元素本身可用严格相等比较uniq更快只有当相等需要自定义规则如模糊匹配、多字段组合、忽略大小写时才应使用uniqWith。三者同属 src/fp/array 目录可对照阅读。复杂度提醒无论 eager 还是 lazy 路径去重逻辑都依赖与已保留集合的线性比较最坏为 O(n²)。处理超大规模数组时可考虑先用uniqBy键值可哈希化做粗过滤。七、总结fp版的uniqWith是理解 es-toolkit 函数式编程子模块的一个绝佳切片它展示了 contenteditable="false">【免费下载链接】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),仅供参考
返回列表