ARTICLE DETAIL

资讯详情

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

sortBy排序原理与实战:从设计模式到多字段排序优化

sortBy排序原理与实战:从设计模式到多字段排序优化 1. sortBy 排序操作到底在排什么第一次接触sortBy这个词很多人会以为它只是某个语言里的一个普通函数名。但如果你在多个技术栈里摸爬滚打过几年就会发现sortBy其实代表了一类非常典型的排序抽象你告诉它“按什么排”它负责“怎么排”。这个思路在函数式编程库、数据库 ORM、前端表格组件、数据处理框架里反复出现只是叫法不同。sortBy的核心价值在于把排序的比较逻辑和排序的执行逻辑解耦。传统写法里你得自己写比较函数比如arr.sort((a, b) a.age - b.age)比较逻辑和排序调用是耦合在一起的。而sortBy让你写成sortBy(arr, age)或者sortBy(arr, item item.age)把“取哪个字段”这件事单独抽出来。这个看似微小的差别在复杂业务里会带来巨大的可维护性差异。这篇文章适合谁看如果你正在写前端表格、做数据报表、处理接口返回的列表数据或者你在用 Lodash、Underscore、Ramda 这类工具库那sortBy几乎是你每天都会碰到的东西。如果你正在学算法想理解工业级排序和教科书排序的区别这篇文章也会给你一个从“能跑”到“好用”的视角。我会从设计思路、核心细节、实操过程到踩坑排查把sortBy这一类排序操作讲透让你看完就能直接抄作业。需要先明确一点sortBy不是某一种具体排序算法它是一个排序接口的设计模式。底层可以用快速排序、归并排序、甚至 Timsort但对外暴露的都是“按某个键或某个函数排序”的统一入口。理解了这一层你再看不同语言和框架里的sortBy就不会被各种 API 差异绕晕。2. 为什么工业代码偏爱 sortBy 而不是裸写比较函数2.1 从“能排序”到“好维护”的跨越教科书教排序重点在算法本身冒泡、选择、插入、希尔、归并、快排、堆排、计数、基数每种算法的时间复杂度、空间复杂度、稳定性都列得清清楚楚。但真实项目里你很少需要自己手写快排。你面对的是一个对象数组每个对象有十几个字段产品经理今天让你按创建时间倒序明天让你按状态优先级再按时间倒序后天又加一个“置顶项永远在最前”的规则。如果每次都用裸的比较函数代码会变成这样list.sort((a, b) { if (a.pinned ! b.pinned) return b.pinned - a.pinned; if (a.status ! b.status) return a.status - b.status; return new Date(b.createdAt) - new Date(a.createdAt); });这段代码能跑但有几个问题。第一比较逻辑嵌在排序调用里复用困难。第二字段顺序和升降序规则散落在 if 里改一个规则要动整个函数。第三测试起来麻烦你得构造完整对象才能验证排序结果。sortBy的思路是把“排序依据”抽成可组合的单元。比如 Lodash 的_.sortBy(list, [pinned, status, createdAt])配合_.orderBy还能指定升降序。这样排序规则变成声明式的一眼就能看出按什么排、什么顺序。改规则只需要改数组不用重写比较逻辑。2.2 稳定排序是业务排序的隐形刚需热词里出现了“稳定排序”这个词这不是偶然。业务排序几乎都依赖稳定性。什么叫稳定就是两个元素如果排序键相等它们在结果里的相对顺序和排序前保持一致。举个例子你有一批订单先按时间倒序排好了。现在产品要求“同一天的订单按金额倒序但同一天内原本时间靠前的仍然靠前”。如果排序算法不稳定你按金额排完之后同金额的订单时间顺序可能被打乱用户看到的就是“乱跳”的列表。稳定排序保证了多轮排序可以叠加先按次要键排再按主要键排最终结果就是“主要键优先、次要键保持上一轮顺序”。JavaScript 的Array.prototype.sort在 ES2019 之后才强制要求稳定之前 V8 对短数组用插入排序稳定长数组用快排不稳定导致同样的代码在不同数据量下结果不一致。这就是为什么很多工具库的sortBy会自己实现归并排序或使用稳定策略而不是直接调原生sort。你在选型时如果发现某个库的sortBy在大数据量下顺序会变大概率就是底层用了不稳定算法。2.3 多字段排序的组合爆炸问题业务排序最麻烦的不是单字段而是多字段组合。假设有 5 个可排序字段每个字段有升序和降序两种选择理论上就有 5×210 种单字段规则再加上多字段组合规则数量会爆炸。如果每个组合都写一个比较函数代码量不可控。sortBy配合orderBy的模式解决了这个问题。你把字段和方向做成配置运行时动态生成比较逻辑。前端表格点击表头排序就是典型场景用户点“姓名”列按姓名升序再点一次按姓名降序按住 Shift 点第二列变成多列排序。这背后就是sortBy在动态接收字段列表和方向列表。热词里还有“点击表头排序”“前端表格加上排序”说明这是前端开发的高频需求。很多 UI 组件库如 Ant Design、Element Plus的 Table 组件都内置了排序能力但底层逻辑和sortBy是一致的把列的sorter配置转换成比较函数再对数据源排序。3. sortBy 的核心细节与实操要点3.1 键提取字符串、函数还是路径sortBy的第一个参数通常是数据集合第二个参数是“排序依据”。这个依据的形态决定了 API 的灵活度。常见的有三种字符串键sortBy(list, age)直接按对象的age属性排。简单直观但只能处理顶层属性。函数sortBy(list, item item.user.age)可以处理嵌套属性、计算值、甚至组合逻辑。灵活但写起来啰嗦。路径数组sortBy(list, [user, age])兼顾简洁和嵌套访问。Lodash 的_.property就支持这种写法。选哪种取决于你的场景。如果只是简单字段字符串键最省事。如果有嵌套或计算函数更直接。如果字段路径是动态的比如从表格列配置里来路径数组最合适。这里有个容易踩的坑字符串键和函数返回值的类型要一致。如果你用字符串键age但某些对象的age是字符串25另一些是数字25排序结果会出乎意料。因为字符串比较是逐字符的100会排在25前面。解决办法是在提取时统一类型比如sortBy(list, item Number(item.age))。3.2 升降序控制orderBy 的两种实现sortBy默认都是升序。要支持降序通常有两种设计第一种是取反。比较函数返回-result就是降序。但这对字符串不适用因为字符串比较返回的是布尔或差值取反后语义不对。所以更通用的做法是反转比较结果如果升序是a b ? 1 : a b ? -1 : 0降序就是a b ? 1 : a b ? -1 : 0。第二种是方向数组。orderBy(list, [age, name], [desc, asc])字段和方向一一对应。这种设计在多字段排序时特别清晰。实现时对每个字段生成一个比较器再按顺序组合。组合比较器的逻辑是先比较第一个字段如果结果不为 0 就直接返回如果为 0继续比较第二个字段以此类推。这就是字典序比较的思想。代码大概长这样function compareByFields(a, b, fields, orders) { for (let i 0; i fields.length; i) { const field fields[i]; const order orders[i] || asc; const va typeof field function ? field(a) : a[field]; const vb typeof field function ? field(b) : b[field]; let result 0; if (va vb) result -1; else if (va vb) result 1; if (result ! 0) { return order desc ? -result : result; } } return 0; }这段代码是sortBy类操作的核心骨架理解了它你自己实现一个也不难。3.3 空值、undefined 和 NaN 的处理真实数据里永远有脏值。null、undefined、空字符串、NaN在排序时怎么处理是区分“玩具代码”和“生产代码”的关键。常见策略有三种空值排最后不管升序降序null和undefined都放末尾。这是最符合用户直觉的因为用户通常不想看到空值占据列表头部。空值排最前某些场景下空值代表“未处理”需要优先关注。按类型默认值处理把null当 0把undefined当空字符串。这种最省事但容易误导。我个人的经验是在提取键的阶段统一处理空值而不是在比较函数里判断。比如sortBy(list, item item.age ?? Infinity)升序时空值自然排最后。如果要降序且空值仍排最后就需要在比较器里特殊处理因为降序会把Infinity翻到最前。NaN更麻烦因为它和任何值比较都返回false包括它自己。NaN 1是falseNaN 1也是false所以比较器会返回 0导致NaN的位置不确定。解决办法是在提取时把NaN转成null或一个明确的哨兵值。注意不要依赖Array.prototype.sort对undefined的默认行为。规范里undefined总是排在最后且不参与比较函数调用。但如果你自己实现sortBy这个行为需要显式处理。3.4 性能考量什么时候不该用 sortBysortBy的抽象有成本。每次比较都要调用键提取函数如果提取函数里有复杂计算排序的O(n log n)次比较会放大这个成本。比如sortBy(list, item heavyCompute(item))heavyCompute会被调用O(n log n)次而不是n次。优化方法是先做 Schwartzian 变换也叫 decorate-sort-undecorate先把每个元素和它的排序键组成一个元组对元组排序再还原。这样键提取只做n次。Lodash 的sortBy内部就做了类似优化。function sortByOptimized(list, keyFn) { return list .map(item ({ item, key: keyFn(item) })) .sort((a, b) (a.key b.key ? -1 : a.key b.key ? 1 : 0)) .map(pair pair.item); }这个模式在处理大数组比如上万条且键计算较重时性能差异非常明显。你可以自己实测一下n10000时可能有几倍的差距。另一个性能点是排序方向。如果数据已经基本有序插入排序或 Timsort 会接近O(n)。但如果你用sortBy先升序再反转来实现降序反转是O(n)整体还是O(n log n)。更好的做法是在比较器里直接处理方向让排序算法利用已有顺序。4. 从零实现一个可用的 sortBy4.1 基础版本支持单字段和多字段我们先写一个最小可用的sortBy支持字符串键、函数键、多字段和升降序。这个版本不追求极致性能但逻辑清晰方便你理解每一步在做什么。function sortBy(list, keys, orders) { // 统一 keys 为数组 const keyList Array.isArray(keys) ? keys : [keys]; const orderList Array.isArray(orders) ? orders : [orders || asc]; // 键提取函数 const getKey (item, key) { if (typeof key function) return key(item); if (Array.isArray(key)) { return key.reduce((obj, k) (obj null ? undefined : obj[k]), item); } return item[key]; }; // 比较两个值 const compareValues (a, b) { if (a b) return 0; if (a null) return 1; // 空值排后 if (b null) return -1; if (typeof a string typeof b string) { return a.localeCompare(b); } return a b ? -1 : a b ? 1 : 0; }; // 复制一份避免修改原数组 return list.slice().sort((a, b) { for (let i 0; i keyList.length; i) { const va getKey(a, keyList[i]); const vb getKey(b, keyList[i]); let result compareValues(va, vb); if (result ! 0) { return orderList[i] desc ? -result : result; } } return 0; }); }这个实现有几个关键决策。第一list.slice()复制数组避免副作用。原生sort会修改原数组这在 React 状态管理里是常见 bug 来源。第二空值统一排后且不受升降序影响因为空值判断在方向取反之前。第三字符串用localeCompare支持中文拼音排序比直接比较更符合用户预期。4.2 进阶版本加入缓存和稳定性保证基础版本每次比较都调用getKey有性能问题。我们加入 Schwartzian 变换同时显式保证稳定性。function sortByStable(list, keys, orders) { const keyList Array.isArray(keys) ? keys : [keys]; const orderList Array.isArray(orders) ? orders : [orders || asc]; const getKey (item, key) { if (typeof key function) return key(item); if (Array.isArray(key)) { return key.reduce((obj, k) (obj null ? undefined : obj[k]), item); } return item[key]; }; // 预计算所有键附带原始索引保证稳定性 const decorated list.map((item, index) ({ item, index, keys: keyList.map(k getKey(item, k)), })); const compareValues (a, b) { if (a b) return 0; if (a null) return 1; if (b null) return -1; if (typeof a string typeof b string) { return a.localeCompare(b); } return a b ? -1 : a b ? 1 : 0; }; decorated.sort((a, b) { for (let i 0; i keyList.length; i) { let result compareValues(a.keys[i], b.keys[i]); if (result ! 0) { return orderList[i] desc ? -result : result; } } // 所有键相等时按原始索引排保证稳定 return a.index - b.index; }); return decorated.map(d d.item); }这个版本把键提取从O(n log n)次降到n次同时用原始索引兜底保证稳定性。即使底层sort不稳定结果也是稳定的。代价是额外的内存开销每个元素多存了一个对象。对于大多数业务场景这个开销可以接受。4.3 参数选择与实测对比我拿 10000 条模拟数据做了个简单对比每条数据有name、age、score三个字段键提取函数里加了一点计算模拟真实场景。实现方式耗时ms内存增量稳定性基础版本48低依赖原生进阶版本22中显式保证原生 sort 裸写18低依赖原生Lodash sortBy25中稳定数据是单次运行的参考值不同环境会有波动。但趋势很明显进阶版本比基础版本快一倍左右接近原生裸写的性能同时多了稳定性和空值处理。Lodash 的表现和进阶版本接近说明它的内部实现思路类似。如果你只是偶尔排个小数组几百条基础版本完全够用代码还更短。如果是表格分页前的全量排序或者数据量上千建议用进阶版本。如果项目已经引入了 Lodash直接用_.orderBy最省事没必要自己造轮子。提示localeCompare在大量调用时可能成为瓶颈。如果不需要本地化排序可以用和直接比较字符串性能会好一些。但中文排序场景下localeCompare是必要的。5. 不同技术栈里的 sortBy 实战5.1 JavaScript 工具库Lodash 与原生对比Lodash 的_.sortBy和_.orderBy是最常用的。区别在于sortBy只支持升序orderBy支持指定方向。用法上import _ from lodash; const users [ { name: 张三, age: 28, score: 90 }, { name: 李四, age: 25, score: 95 }, { name: 王五, age: 28, score: 85 }, ]; // 按 age 升序 _.sortBy(users, age); // 按 age 升序age 相同按 score 降序 _.orderBy(users, [age, score], [asc, desc]);Lodash 的orderBy在稳定性上表现很好而且支持路径字符串user.age。但要注意Lodash 的sortBy对undefined的处理和原生不同它会把undefined排在最后但null会被当作正常值参与比较可能排在前面。如果你的数据里有null最好先过滤或转换。原生Array.prototype.sort在 ES2019 后稳定但比较函数要自己写。如果你不想引入 Lodash可以封装一个小的sortBy工具函数就像上面进阶版本那样。我的建议是如果项目里已经有 Lodash直接用如果没有且排序需求不复杂自己封装一个 50 行的工具函数比引入整个 Lodash 更划算。5.2 前端表格排序点击表头背后的逻辑前端表格排序是sortBy的高频应用场景。以 Ant Design Table 为例你可以在列配置里写sorterconst columns [ { title: 姓名, dataIndex: name, sorter: (a, b) a.name.localeCompare(b.name), }, { title: 年龄, dataIndex: age, sorter: (a, b) a.age - b.age, defaultSortOrder: descend, }, ];但这是单列排序。如果要支持多列排序需要自己管理sortOrder状态把多个列的排序规则组合起来。常见做法是维护一个sortState数组每次点击表头时更新这个数组然后用sortBy对数据源排序。function applySort(data, sortState) { if (!sortState.length) return data; const keys sortState.map(s s.field); const orders sortState.map(s s.order ascend ? asc : desc); return sortByStable(data, keys, orders); }这里有个细节表格分页时排序应该作用于全量数据还是当前页正确答案是全量数据。如果只排当前页用户翻页后会看到顺序错乱。所以排序要在分页之前做或者由后端排序。前端排序适合数据量不大的场景比如几千条数据量大时应该把排序参数传给后端。5.3 数据库与 ORM 中的排序从 SQL 到 Sequelize后端排序和前端排序的思路一致只是执行位置不同。SQL 的ORDER BY就是数据库层面的sortBySELECT * FROM users ORDER BY age ASC, score DESC;Sequelize 作为 Node.js 的 ORM提供了order选项User.findAll({ order: [ [age, ASC], [score, DESC], ], });热词里出现了“sequelize别名排序”这是个容易踩坑的点。当你用了include关联查询并给关联表起了别名排序字段需要写成[{ model: Comment, as: comments }, createdAt, DESC]这种形式。如果直接写comments.createdAtSequelize 可能找不到字段。解决办法是查文档确认别名路径或者用sequelize.literal直接写 SQL 片段。MySQL 的排序还有一个经典问题ORDER BY的字段如果没有索引会触发 filesort数据量大时很慢。优化方法是给排序字段加索引或者用覆盖索引让排序在索引里完成。热词里的“mysql排序”和“mq-deadline按扇区排序原理”虽然领域不同但都指向同一个道理排序的性能取决于数据组织和访问模式。5.4 Python 与数据分析DataFrame 排序Python 的 Pandas 里排序用sort_values和sort_indeximport pandas as pd df pd.DataFrame({ name: [张三, 李四, 王五], age: [28, 25, 28], score: [90, 95, 85], }) # 按 age 升序score 降序 df.sort_values(by[age, score], ascending[True, False])Pandas 的sort_values默认使用快速排序但可以通过kind参数指定mergesort来保证稳定性。多字段排序时ascending接收一个布尔列表和by一一对应。这个设计和 Lodash 的orderBy非常相似说明多字段排序的接口设计在跨语言时趋同。DataFrame 排序的一个注意点是inplace参数。默认inplaceFalse返回新对象原 DataFrame 不变。如果你写成df.sort_values(..., inplaceTrue)原数据会被修改且返回None。这个坑我见过不少人踩尤其是在链式调用里。6. 常见问题与排查技巧实录6.1 排序结果不符合预期先查这五个点排序出问题时不要急着改代码按顺序排查这五个点能解决大部分问题。排查项常见症状解决方法数据类型不一致数字被当字符串排10排在9前提取键时统一Number()或String()空值处理缺失null/undefined跑到列表头部比较器中显式处理空值稳定性依赖相同键的元素顺序随机用稳定排序或加原始索引兜底原地修改排序后原数组也变了排序前slice()复制多字段顺序错误次要字段覆盖了主要字段检查比较器循环顺序和方向数组我遇到最多的是第一条。后端返回的 JSON 里数字字段有时是字符串比如从数据库 decimal 类型序列化而来前端直接排就会出错。解决办法是在数据进入排序前做一次类型规范化或者在sortBy的键提取函数里强制转换。6.2 中文排序为什么和英文不一样中文排序比英文复杂因为汉字没有天然的顺序。localeCompare默认按 Unicode 码点排但用户期望的是拼音顺序。要按拼音排需要指定 locale张三.localeCompare(李四, zh-CN); // 返回负数因为 zhāng 在 lǐ 前面如果不指定zh-CN在某些环境下可能按笔画或码点排结果和用户预期不符。更严谨的做法是用Intl.Collatorconst collator new Intl.Collator(zh-CN, { sensitivity: base }); [张三, 李四, 王五].sort(collator.compare);Intl.Collator的好处是可以复用避免每次比较都创建新的 collator性能更好。sensitivity: base表示忽略大小写和音调适合大多数业务场景。6.3 大数据量排序卡顿的优化思路数据量上万后前端排序可能卡顿。优化方向有几个第一减少比较次数。如果只需要前 N 条用部分排序如堆排序取 Top K而不是全量排序。时间复杂度从O(n log n)降到O(n log k)。第二把排序放到 Web Worker。排序是纯计算不涉及 DOM很适合放到 Worker 里避免阻塞主线程。数据通过postMessage传递排完再传回来。第三后端排序。数据量超过几万条时前端排序的传输成本和计算成本都不划算。把排序字段和方向传给后端让数据库用索引排序前端只负责展示。第四虚拟滚动。如果只是渲染卡顿排序本身不慢可以用虚拟滚动只渲染可视区域的行。这和排序无关但能显著提升大列表的交互体验。提示Array.prototype.sort在 V8 里对超过 10 个元素的数组用 Timsort对短数组用插入排序。Timsort 是稳定的且对部分有序数据有优化。所以大多数情况下原生sort的性能已经足够好瓶颈往往在键提取和 DOM 渲染上。6.4 排序与分页的配合陷阱排序和分页一起用时最容易出的问题是“翻页后顺序变了”。原因通常是只对当前页数据排序而不是全量数据。正确流程是先排序全量数据再分页截取。另一个陷阱是后端分页 前端排序。如果后端返回的是第 2 页数据前端只对这 10 条排序用户看到的“全局排序”是错的。这种场景必须把排序参数传给后端由后端统一排序和分页。还有一种情况是排序字段不在当前页数据里。比如按score排序但当前页只返回了name和age。前端拿不到score无法排序。解决办法是让后端返回排序字段或者干脆后端排序。7. 排序算法选型从业务需求反推技术方案7.1 稳定性、时间复杂度与数据规模的三角权衡选排序算法不是选“最好的”而是选“最合适的”。三个维度互相制约稳定性、时间复杂度、实现复杂度。算法平均时间复杂度最坏时间复杂度稳定性适用场景冒泡排序O(n²)O(n²)稳定教学、极小数据选择排序O(n²)O(n²)不稳定教学、交换成本高插入排序O(n²)O(n²)稳定小数据、基本有序希尔排序O(n^1.3)O(n²)不稳定中等数据归并排序O(n log n)O(n log n)稳定大数据、要求稳定快速排序O(n log n)O(n²)不稳定大数据、内存敏感堆排序O(n log n)O(n log n)不稳定大数据、取 Top K计数排序O(nk)O(nk)稳定整数、范围小基数排序O(d(nk))O(d(nk))稳定整数、位数少业务排序里归并排序和 Timsort 是最常用的因为它们稳定且最坏情况也是O(n log n)。快速排序虽然平均快但最坏情况O(n²)在业务里不可接受除非你做了随机化或三数取中。堆排序适合“只要前 K 个”的场景不需要全量排序。热词里还有“拓扑排序”这是另一类问题不是给数字排序而是给有依赖关系的任务排序。比如编译顺序、课程安排、任务调度。拓扑排序的结果不唯一只要满足所有依赖关系即可。实现上用 Kahn 算法或 DFS时间复杂度O(VE)。如果你在做工作流引擎或构建工具拓扑排序是核心算法。7.2 从 sortBy 到 orderBy接口设计的演进sortBy只支持升序orderBy支持指定方向。这个演进反映了业务需求的复杂化。早期工具库只有sortBy因为简单场景够用。后来发现降序是刚需就加了orderBy。再往后出现了更复杂的排序需求按自定义优先级排、按多个字段不同方向排、按计算值排。接口设计也跟着演进。Lodash 的orderBy支持字段数组和方向数组基本覆盖了大多数场景。更复杂的场景就需要自己写比较器了。从接口设计角度sortBy类函数的参数顺序通常是(collection, iteratees, orders)。iteratees可以是字符串、函数或数组orders是方向数组。这种设计的好处是向后兼容老代码只传collection和iteratees新代码可以加orders。7.3 排序统计与监控怎么知道排序慢不慢生产环境里排序性能需要监控。尤其是后端接口排序慢会直接拖慢响应时间。监控指标包括排序耗时、排序数据量、排序字段、是否触发 filesort。MySQL 可以用EXPLAIN查看ORDER BY是否用了索引。如果Extra列出现Using filesort说明排序没有走索引数据量大时需要优化。优化方法是加联合索引让WHERE和ORDER BY的字段顺序匹配索引顺序。前端可以用performance.now()打点const start performance.now(); const sorted sortByStable(data, keys, orders); const duration performance.now() - start; if (duration 100) { console.warn(排序耗时 ${duration}ms数据量 ${data.length}); }超过 100ms 的排序用户能感知到卡顿需要考虑优化或放到 Worker 里。这个阈值不是绝对的取决于设备性能但可以作为参考线。8. 我踩过的坑和几条实用建议先说一个我印象最深的坑。有一次做订单列表按金额降序排测试环境没问题上线后用户反馈“金额相同的订单顺序会变”。排查后发现测试数据金额都不同没触发稳定性问题。线上有大量相同金额的订单而当时用的排序库底层是不稳定排序导致相同金额的订单顺序随机。后来换成稳定排序问题解决。这件事让我养成了一个习惯只要业务排序可能遇到重复键就必须确认排序的稳定性。第二个坑是空值。有次按“最后登录时间”排序期望最近登录的在前。结果一批从未登录的用户lastLogin为null排在了最前面因为null在比较时被当成了最小值。用户看到的就是“一堆从没登录过的账号占据榜首”。后来改成空值排最后体验才正常。所以空值处理不是可选项是必选项。第三个坑是性能。有个报表页面要排 5 万条数据每次排序卡 2 秒。后来发现键提取函数里做了new Date()转换O(n log n)次调用把时间解析放大了十几倍。改成先转换再排序耗时降到 200ms 以内。这个优化思路就是前面说的 Schwartzian 变换值得你记在脑子里。几条实用建议排序前先复制数组别改原数据。React 状态、Vue 响应式数据被原地排序后可能不触发更新或者触发意外的重渲染。多字段排序时把区分度高的字段放前面。比如先按状态排再按时间排比反过来更符合业务直觉。如果排序规则来自用户配置做好默认值和边界处理。用户可能传空数组、不存在的字段、非法方向这些都要兜住。后端排序优先于前端排序。数据量超过几千条时把排序交给数据库前端只负责展示。测试排序时一定要构造包含重复键、空值、极端值的用例。正常数据往往测不出问题。最后分享一个小技巧如果你不确定某个排序库的行为写个 10 条数据的测试用例包含重复键和空值跑一遍看结果。这比读文档快得多也更可靠。排序这东西行为细节比 API 签名重要得多。
返回列表