news 2026/9/16 17:37:26

es-toolkit/fp 函数式 sortBy:以 pipe 组合多条件升序排序的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
es-toolkit/fp 函数式 sortBy:以 pipe 组合多条件升序排序的完整指南

es-toolkit/fp 函数式 sortBy:以 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

本篇技术指南聚焦 es-toolkit 函数式模块es-toolkit/fp中的sortBy,讲解如何用它创建"按一个或多个条件升序排序"的纯函数,并与pipe组合实现数据流式处理。读完本文,你将掌握sortBy的参数签名、多键/选择器函数的用法、底层排序原理(委托orderBycompareValues的比较逻辑),以及它与普通es-toolkit/arraysortBy的取舍。

一、背景:为什么需要 fp 版的 sortBy

es-toolkit 同时提供了两套 API:普通命令式(如es-toolkit/array中的sortBy(arr, criteria))与 函数式 fp 模块(es-toolkit/fp)。两者功能一致,但参数顺序与调用形态不同

  • 普通版:sortBy(arr, criteria)—— 数据在前,配置在后,一次性完成排序。
  • fp 版:sortBy(criteria)—— 先接收配置(排序条件),返回一个"等待数据"的函数,即>const result = pipe(array, sortBy(criteria));

    fp 版的返回值签名是(array: readonly T[]) => T[],恰好是 pipe 中"操作符函数"的形态:pipe把初始值从左到右依次穿过每个函数,前一个函数的输出作为后一个函数的输入(源码见 src/fp/pipe.ts)。因此sortBy(criteria)可以像map(fn)filter(fn)一样直接嵌入管道,与其他变换自然组合。

    官方文档明确建议:普通代码优先使用原版sortBy;只有当你需要借助pipe组合一系列变换时才使用 fp 变体

    二、基本用法:单键、多键与选择器函数

    sortBy按升序排列对象数组。每个 criterion(排序条件)可以是对象键返回待比较值的函数;当两个元素在当前条件上并列时,使用下一个条件打破平局。排序是稳定的(stable),且不会修改输入数组。

    import { pipe, sortBy } from 'es-toolkit/fp'; const users = [ { user: 'foo', age: 24 }, { user: 'bar', age: 7 }, { user: 'foo', age: 8 }, { user: 'bar', age: 29 }, ]; // 按单个键排序。 pipe(users, sortBy(['age'])); // => [{ user: 'bar', age: 7 }, { user: 'foo', age: 8 }, { user: 'foo', age: 24 }, { user: 'bar', age: 29 }] // 按多个条件排序,用下一个条件打破平局。 pipe(users, sortBy(['user', 'age'])); // => [{ user: 'bar', age: 7 }, { user: 'bar', age: 29 }, { user: 'foo', age: 8 }, { user: 'foo', age: 24 }] // 也可以用选择器函数代替键。 pipe(users, sortBy([item => item.age]));

    参数

    • criteriaArray<((item: T) => unknown) | keyof T>):用于比较的对象键和/或选择器函数,按顺序依次应用。类型约束为T extends object,即排序目标是对象数组。

    返回值

    (array: readonly T[]) => T[]):一个将readonly T[]映射为新的、已排序T[]的函数。

    三、源码剖析:一层柯里化包装 + 委托核心排序

    fp 版sortBy的实现非常精简,本质是对普通版的一层"配置先行"包装。完整源码见 src/fp/array/sortBy.ts:

    import { sortBy as sortByToolkit } from '../../array/sortBy.ts'; export function sortBy<T extends object>( criteria: ReadonlyArray<((item: T) => unknown) | keyof T> ): (array: readonly T[]) => T[] { return function (array: readonly T[]): T[] { return sortByToolkit(array, criteria); }; }

    外层函数只负责"记住"criteria,返回的闭包在收到数组时才真正调用底层实现 src/array/sortBy.ts。而普通版本身又是一个薄壳,把排序方向固定为升序后直接委托给更通用的orderBy

    // src/array/sortBy.ts export function sortBy<T extends object>( arr: readonly T[], criteria: ReadonlyArray<((item: T) => unknown) | keyof T> ): T[] { return orderBy(arr, criteria, ['asc']); }

    因此完整的调用链是:fp/sortBy(criteria)array/sortBy(arr, criteria)orderBy(arr, criteria, ['asc'])。多条件排序的逐条比较逻辑集中在 src/array/orderBy.ts:

    return arr.slice().sort((a, b) => { const ordersLength = orders.length; for (let i = 0; i < criteria.length; i++) { const order = ordersLength > i ? orders[i] : orders[ordersLength - 1]; const criterion = criteria[i]; const criterionIsFunction = typeof criterion === 'function'; const valueA = criterionIsFunction ? criterion(a) : a[criterion]; const valueB = criterionIsFunction ? criterion(b) : b[criterion]; const result = compareValues(valueA, valueB, order); if (result !== 0) { return result; } } return 0; });

    从源码可以确认三个关键实现事实:

    1. 不修改输入:排序前先arr.slice()拷贝一份,再调用数组原生sort,原始数组保持不变。
    2. 多条件按序生效:比较函数按criteria数组的顺序逐条比较;某条件比较结果非 0 立即返回,为 0(并列)才进入下一个条件;全部条件都并列时返回 0,交由原生sort的稳定性保证原始相对顺序。
    3. 键与函数统一处理typeof criterion === 'function'时视为选择器执行,否则视为对象键a[criterion]直接取值。

    底层的比较语义:compareValues

    单条件的比较结果由 src/_internal/compareValues.ts 计算。compareValues(a, b, order)在升序时做compareAscending(a, b),降序时交换操作数实现反转。值得注意的细节是它对null / undefined 的专门处理

    • 普通值排在最前(rank 0),null其次(rank 1),undefined最后(rank 2);
    • 同类空值之间视为相等(返回 0),保持原有相对顺序;
    • 两个普通值按 JavaScript 原生</>关系比较。

    这意味着即使排序字段存在缺失值,sortBy也不会抛错,而是把空值稳定地排到末尾。虽然 fp 版公开 API 只提供升序,但底层复用同一个比较器,因此该空值语义对 fp 版同样成立。

    四、更复杂的组合:选择器函数与管道编排

    键可以混合选择器函数使用,适合"按计算值排序"的场景。例如按分类优先、再按价格从高到低:

    import { pipe, sortBy } from 'es-toolkit/fp'; const products = [ { name: 'laptop', price: 1000, category: 'electronics' }, { name: 'shirt', price: 50, category: 'clothing' }, { name: 'phone', price: 800, category: 'electronics' }, ]; pipe( products, sortBy([ 'category', item => -item.price, // 取负实现"降序",因为 sortBy 本身只升序 ]) );

    借助pipe可以把它嵌入更大的数据处理流。需要注意:fp 版的pipe对连续的惰性函数(mapfiltertake等)会做融合与短路优化(见 pipe 文档),而sortBy属于需要完整数组的急切(eager)操作,因此排序通常放在管道中靠后的位置,例如"先过滤、再排序、最后截取":

    import { filter, pipe, sortBy, take } from 'es-toolkit/fp'; pipe( users, filter(user => user.age >= 18), sortBy(['age']), take(3) // 取年龄最小的前 3 位成年用户 );

    五、测试验证:稳定性与不可变性

    fp 版sortBy的行为由 src/fp/array/sortBy.spec.ts 覆盖验证,测试直接通过pipe(users, sortBy(...))断言结果:

    测试用例断言内容
    单键排序sortBy(['age'])得到按 age 升序的完整数组
    多键排序sortBy(['user', 'age'])先按 user 再按 age 打破平局
    选择器函数sortBy([(item) => item.age])与按键排序结果一致
    不可变性传入数组副本执行排序后,原数组内容保持不变

    测试用例与文档示例保持一致,读者可以把这些用例当作"可运行的最小示例"来理解 API 语义。

    六、总结与选型建议

    • fp 版sortBy(criteria):data-last、可组合,适合在pipe管道中使用,返回的是"待数据函数";
    • 普通版sortBy(arr, criteria):data-first、一次调用即得结果,适合在普通命令式代码中直接使用;
    • 排序语义:升序、稳定、多条件依次破平、不修改输入、空值排后;
    • 底层链路:fp 包装 → 普通 sortBy → orderBy 多条件比较 → compareValues 值比较。

    需要降序排序时,官方建议直接使用支持'desc'方向的 orderBy(它接收orders参数),或在选择器函数中对值取负、取倒数等技巧在升序框架内实现反向效果。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 17:36:22

抖音合集批量下载工具:3 步跑通 douyin-downloader 无水印下载

抖音合集批量下载工具&#xff1a;3 步跑通 douyin-downloader 无水印下载 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallba…

作者头像 李华
网站建设 2026/9/16 17:35:51

BWAPI 4.4.0 环境配置与 ualbertabot 编译实战:星际 AI Bot 跑通指南

先说一个可能很多人都有过的经历&#xff1a;折腾半天把 BWAPI 官方例程编出来了&#xff0c;一加载进星际争霸就黑屏或者 Bot 完全不动&#xff0c;最后才发现根本不是代码问题&#xff0c;而是环境配错了。星际争霸的 AI 开发入门的门槛其实不低&#xff0c;BWAPI 的版本、游…

作者头像 李华
网站建设 2026/9/16 17:34:37

WeChatMsg 免费开源微信聊天记录导出工具:3步完成本地备份

WeChatMsg 免费开源微信聊天记录导出工具&#xff1a;3步完成本地备份 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/W…

作者头像 李华
网站建设 2026/9/16 17:34:35

Pinocchio零知识证明库实战:可验证计算工程落地指南

1. 项目概述&#xff1a;这不是童话&#xff0c;是密码学工程现场“Show HN: Pinocchio: Harness for Verifiable Work”——这个标题一出现&#xff0c;我就立刻停下手头三个正在跑的零知识证明&#xff08;ZKP&#xff09;验证任务&#xff0c;把终端窗口最小化&#xff0c;点…

作者头像 李华
网站建设 2026/9/16 17:34:09

GroundingDINO 配置选型指南:SwinT 与 SwinB 选型对比

GroundingDINO 配置选型指南&#xff1a;SwinT 与 SwinB 选型对比 【免费下载链接】GroundingDINO [ECCV 2024] Official implementation of the paper "Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection" 项目地址: http…

作者头像 李华