最快、最简单的方法是使用OR链接,正如许多人在这里已经建议的那样。对于指定的示例数据,它看起来像这样:
homes.sort((a, b) =>
a.city.localeCompare(b.city)
|| (Number(b.price) - Number(a.price))
);
但是如果你想要可配置的内容(并且使用 TypeScript),你可以尝试下面的代码:
代码(TypeScript)
export type Comparer<T> = (a: T, b: T) => number;
export type CompareCriterion<TItem, TValue> = {
selector: (item: TItem) => TValue,
descending?: boolean,
comparer?: Comparer<TValue>,
};
export const defaultComparer = <T>(a: T, b: T): number => {
return a === b ? 0 : a > b ? 1 : -1;
};
export const defaultNumberComparer = (a: number, b: number): number => {
return a - b;
};
export const StringComparer = (() => {
const currentLocale = new Intl.Collator(navigator.language, { usage: 'sort', sensitivity: 'variant', caseFirst: 'upper' });
const currentLocaleIgnoreCase = new Intl.Collator(navigator.language, { usage: 'sort', sensitivity: 'accent', caseFirst: 'upper' });
const invariantLocale = new Intl.Collator('en', { usage: 'sort', sensitivity: 'variant', caseFirst: 'upper' });
const invariantLocaleIgnoreCase = new Intl.Collator('en', { usage: 'sort', sensitivity: 'accent', caseFirst: 'upper' });
return {
currentLocale: currentLocale.compare,
currentLocaleIgnoreCase: currentLocaleIgnoreCase.compare,
invariantLocale: invariantLocale.compare,
invariantLocaleIgnoreCase: invariantLocaleIgnoreCase.compare,
};
})();
export const defaultStringComparer = (a: string, b: string): number => {
return a.localeCompare(b);
};
export const defaultDateComparer = (a: Date, b: Date): number => {
return a.getTime() - b.getTime();
};
export class ComparerBuilder<TItem> {
#criteria: ((next?: Comparer<TItem>) => Comparer<TItem>)[] = [];
add<TValue>(criterion: CompareCriterion<TItem, TValue>): ComparerBuilder<TItem> {
this.#criteria.push(next => ComparerBuilder.#createComparer(criterion, next));
return this;
}
static #createComparer<TItem, TValue>(
criterion: CompareCriterion<TItem, TValue>,
next?: Comparer<TItem>,
): Comparer<TItem> {
const comparer = criterion.comparer ?? defaultComparer;
return (a: TItem, b: TItem) => {
const av = criterion.selector(a);
const bv = criterion.selector(b);
const comparison = comparer(av, bv);
if (comparison === 0)
return next?.(a, b) ?? 0;
return criterion.descending ? -comparison : comparison;
};
}
build(bottomComparer?: Comparer<TItem>): Comparer<TItem> {
let comparer = bottomComparer;
for (let i = this.#criteria.length - 1; i >= 0; i--)
comparer = this.#criteria[i](comparer);
return comparer ?? defaultComparer;
}
}
使用示例
type Item = { key: number, code: string, name: string, price: number };
const comparer = new ComparerBuilder<Item>()
.add({ selector: v => v.price })
.add({ selector: v => v.code, descending: true, comparer: StringComparer.currentLocaleIgnoreCase })
.add({ selector: v => v.name, comparer: new Intl.Collator('ru').compare })
.add({ selector: v => v.key, comparer: defaultNumberComparer })
.build();
const items1: Item[] = [{ key: 1, code: 'FOO', name: 'bar', price: 100.98 }, { key: 2, code: 'FOa', name: 'baz', price: 100.98 }];
const sortedItems1 = [...items1].sort(comparer);
const items2: Item[] = [{ key: 1, code: 'BAR', name: 'foo', price: 100.98 }];
const sortedItems2 = [...items2].sort(comparer);
sort(["first-field", "ASC"], ["second-field", "DSC"]);
。当我尝试添加第一个答案的“primer”逻辑以处理日期、大小写不敏感等时,这变得更加复杂。 - Mikehomes.sort((a, b) =>
…)
,其中a.prop
和b.prop
。a.prop - b.prop
数值排序,a.prop.localeCompare(b.prop)
字典序排序,(b.prop < a.prop) - (a.prop < b.prop)
通用排序。要按降序排序而不是升序,请取反返回值(例如,使用b.prop - a.prop
而不是a.prop - b.prop
)。 - Sebastian Simon