实现类似 C# orderBy 和 thenBy 的 JavaScript 多字段排序

实现类似 C# orderBythenBy 的 JavaScript 多字段排序

在开发过程中,我们经常需要对数组数据进行多字段排序。例如,我们可能希望先按姓名排序,如果姓名相同再按年龄排序,最后如果还相同,则按分数排序。这种需求在 C# 中通过 orderBythenBy 很容易实现。那么在 JavaScript 中,我们如何实现类似的功能呢?

基础排序:Array.prototype.sort

JavaScript 提供了原生的 Array.prototype.sort 方法,它接受一个比较函数作为参数:

array.sort((a, b) => {
    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
});

这个方法可以用来对数组进行单字段排序,但如果需要多字段排序,我们需要进一步扩展。

核心实现:链式比较器

在多字段排序中,排序规则可以看作是一系列优先级递减的比较规则:

  1. 先按照第一个字段排序。
  2. 如果第一个字段相等,再比较第二个字段。
  3. 如果前两个字段都相等,再比较第三个字段,以此类推。

实现这一逻辑的关键是 逐一尝试多个比较器,直到找到第一个决定性结果

排序工具类实现

以下是一个实现了 orderBythenBy 的工具类:

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,表示相等
    });
}

解读

  1. this.comparators 是一个存储多个比较器的数组。
  2. 使用 for...of 遍历所有比较器。
  3. 每个比较器会比较当前的两个元素 (ab),如果比较结果非零,说明顺序已经确定,立即返回结果。
  4. 如果所有比较器都返回 0,表示两个元素在所有规则下相等。

为什么需要逐一尝试比较器?

多字段排序的需求本质上是一个“优先级递减的链式比较”。

  • 第一个字段最重要:如果第一个字段能决定顺序,后续字段无需比较。
  • 第一个字段相同时:才会进入第二个字段的比较规则,依此类推。

比较器的灵活性

每个比较器的实现可以通过 selector 参数灵活定义,例如:

  • 按对象属性排序:item => item.age
  • 按计算值排序:item => item.name.length
  • 按复合逻辑排序:item => item.score / item.age

通过这种方式,我们可以轻松实现各种复杂的排序需求。

总结

通过实现一个 Sorter 工具类,我们可以在 JavaScript 中轻松实现类似 C# 的多字段排序功能。关键点在于:

  • 比较器的链式调用orderBythenBy 的设计保证了排序规则的优先级。
  • 逐一尝试规则:在 sort 方法中依次执行多个比较器,找到第一个非零结果。

这种方式不仅简单直观,而且非常灵活,可适配各种排序需求。

posted @ 2024-12-09 15:44  灵火  阅读(92)  评论(0)    收藏  举报