实现类似 C# orderBy 和 thenBy 的 JavaScript 多字段排序
实现类似 C# orderBy 和 thenBy 的 JavaScript 多字段排序
在开发过程中,我们经常需要对数组数据进行多字段排序。例如,我们可能希望先按姓名排序,如果姓名相同再按年龄排序,最后如果还相同,则按分数排序。这种需求在 C# 中通过 orderBy 和 thenBy 很容易实现。那么在 JavaScript 中,我们如何实现类似的功能呢?
基础排序:Array.prototype.sort
JavaScript 提供了原生的 Array.prototype.sort 方法,它接受一个比较函数作为参数:
array.sort((a, b) => {
if (a > b) return 1;
if (a < b) return -1;
return 0;
});
这个方法可以用来对数组进行单字段排序,但如果需要多字段排序,我们需要进一步扩展。
核心实现:链式比较器
在多字段排序中,排序规则可以看作是一系列优先级递减的比较规则:
- 先按照第一个字段排序。
- 如果第一个字段相等,再比较第二个字段。
- 如果前两个字段都相等,再比较第三个字段,以此类推。
实现这一逻辑的关键是 逐一尝试多个比较器,直到找到第一个决定性结果。
排序工具类实现
以下是一个实现了 orderBy 和 thenBy 的工具类:
class Sorter {
constructor(data) {
this.data = [...data]; // 拷贝数据,避免修改原数组
this.comparators = []; // 存储比较器函数的数组
}
orderBy(selector, order = 'asc') {
this.comparators.push(this.getComparator(selector, order));
return this; // 支持链式调用
}
thenBy(selector, order = 'asc') {
return this.orderBy(selector, order); // 简化实现,复用 orderBy
}
getComparator(selector, order) {
const direction = order === 'asc' ? 1 : -1;
return (a, b) => {
const valueA = selector(a);
const valueB = selector(b);
if (valueA > valueB) return direction;
if (valueA < valueB) return -direction;
return 0; // 如果相等,返回 0
};
}
sort() {
return this.data.sort((a, b) => {
for (const comparator of this.comparators) {
const result = comparator(a, b);
if (result !== 0) return result; // 找到决定性结果,立即返回
}
return 0; // 所有比较器都返回 0,表示完全相等
});
}
}
示例使用
const data = [
{ name: 'Alice', age: 25, score: 85 },
{ name: 'Bob', age: 30, score: 90 },
{ name: 'Alice', age: 25, score: 80 },
{ name: 'Charlie', age: 25, score: 92 },
{ name: 'Alice', age: 22, score: 80 },
];
const sortedData = new Sorter(data)
.orderBy(item => item.name, 'asc') // 按 name 升序
.thenBy(item => item.age, 'asc') // 如果 name 相同,按 age 升序
.thenBy(item => item.score, 'desc') // 如果前两者相同,按 score 降序
.sort();
console.log(sortedData);
输出结果
[
{ name: 'Alice', age: 22, score: 80 },
{ name: 'Alice', age: 25, score: 85 },
{ name: 'Alice', age: 25, score: 80 },
{ name: 'Bob', age: 30, score: 90 },
{ name: 'Charlie', age: 25, score: 92 }
]
核心逻辑解析
以下是关键逻辑部分:
sort() {
return this.data.sort((a, b) => {
for (const comparator of this.comparators) {
const result = comparator(a, b);
if (result !== 0) return result; // 找到非零结果,决定顺序
}
return 0; // 所有规则都返回 0,表示相等
});
}
解读
this.comparators是一个存储多个比较器的数组。- 使用
for...of遍历所有比较器。 - 每个比较器会比较当前的两个元素 (
a和b),如果比较结果非零,说明顺序已经确定,立即返回结果。 - 如果所有比较器都返回 0,表示两个元素在所有规则下相等。
为什么需要逐一尝试比较器?
多字段排序的需求本质上是一个“优先级递减的链式比较”。
- 第一个字段最重要:如果第一个字段能决定顺序,后续字段无需比较。
- 第一个字段相同时:才会进入第二个字段的比较规则,依此类推。
比较器的灵活性
每个比较器的实现可以通过 selector 参数灵活定义,例如:
- 按对象属性排序:
item => item.age - 按计算值排序:
item => item.name.length - 按复合逻辑排序:
item => item.score / item.age
通过这种方式,我们可以轻松实现各种复杂的排序需求。
总结
通过实现一个 Sorter 工具类,我们可以在 JavaScript 中轻松实现类似 C# 的多字段排序功能。关键点在于:
- 比较器的链式调用:
orderBy和thenBy的设计保证了排序规则的优先级。 - 逐一尝试规则:在
sort方法中依次执行多个比较器,找到第一个非零结果。
这种方式不仅简单直观,而且非常灵活,可适配各种排序需求。

浙公网安备 33010602011771号