Array.prototype.sort()
sort()
方法是 Array
实例的方法,它就地对数组的元素进行排序,并返回对同一数组的引用,现在该数组已排序。默认排序顺序为升序,基于将元素转换为字符串,然后比较其 UTF-16 代码单元值序列。
排序的时间和空间复杂度无法保证,因为它取决于实现。
要对数组中的元素进行排序而不修改原始数组,请使用 toSorted()
。
试一试
语法
sort()
sort(compareFn)
参数
返回值
对原始数组的引用,现在已排序。请注意,数组是就地排序的,并且没有创建副本。
描述
如果未提供 compareFn
,则所有非 undefined
数组元素都将通过将其转换为字符串并在 UTF-16 代码单元顺序中比较字符串来排序。例如,“banana” 在“cherry” 之前。在数字排序中,9 在 80 之前,但由于数字被转换为字符串,因此“80” 在 Unicode 顺序中在“9” 之前。所有 undefined
元素都排序到数组的末尾。
sort()
方法保留空插槽。如果源数组是稀疏的,则空插槽将移动到数组的末尾,并且始终位于所有 undefined
之后。
注意:在 UTF-16 中,高于 \uFFFF
的 Unicode 字符被编码为两个代理代码单元,范围为 \uD800
- \uDFFF
。每个代码单元的值都会单独考虑以进行比较。因此,由代理对 \uD855\uDE51
形成的字符将在字符 \uFF3A
之前排序。
如果提供了 compareFn
,则所有非 undefined
数组元素都将根据比较函数的返回值进行排序(所有 undefined
元素都排序到数组的末尾,并且不会调用 compareFn
)。
compareFn(a, b) 返回值 |
排序顺序 |
---|---|
> 0 | 将 a 排序到 b 之后,例如 [b, a] |
< 0 | 将 a 排序到 b 之前,例如 [a, b] |
=== 0 | 保持 a 和 b 的原始顺序 |
因此,比较函数具有以下形式
function compareFn(a, b) {
if (a is less than b by some ordering criterion) {
return -1;
} else if (a is greater than b by the ordering criterion) {
return 1;
}
// a must be equal to b
return 0;
}
更正式地说,比较器应具有以下属性,以确保正确的排序行为
- 纯:比较器不修改正在比较的对象或任何外部状态。(这很重要,因为无法保证何时以及如何调用比较器,因此任何特定调用都不应对外部产生可见影响。)
- 稳定:比较器对同一对输入返回相同的结果。
- 自反:
compareFn(a, a) === 0
。 - 反对称:
compareFn(a, b)
和compareFn(b, a)
必须都为0
或具有相反的符号。 - 传递:如果
compareFn(a, b)
和compareFn(b, c)
都为正、零或负,则compareFn(a, c)
具有与前两个相同的正性。
符合上述约束的比较器始终能够返回所有 1
、0
和 -1
,或始终返回 0
。例如,如果比较器仅返回 1
和 0
,或仅返回 0
和 -1
,则它将无法可靠地排序,因为反对称性被破坏。始终返回 0
的比较器会导致数组根本不发生更改,但仍然可靠。
默认的词典比较器满足上述所有约束。
要比较数字而不是字符串,比较函数可以从 a
中减去 b
。以下函数将按升序对数组进行排序(如果它不包含 NaN
)
function compareNumbers(a, b) {
return a - b;
}
sort()
方法是泛型的。它仅期望 this
值具有 length
属性和整数键属性。尽管字符串也是类似数组的,但此方法不适合应用于它们,因为字符串是不可变的。
示例
创建、显示和排序数组
以下示例创建四个数组并显示原始数组,然后显示排序后的数组。数字数组在没有比较函数的情况下进行排序,然后使用比较函数进行排序。
const stringArray = ["Blue", "Humpback", "Beluga"];
const numberArray = [40, 1, 5, 200];
const numericStringArray = ["80", "9", "700"];
const mixedNumericArray = ["80", "9", "700", 40, 1, 5, 200];
function compareNumbers(a, b) {
return a - b;
}
stringArray.join(); // 'Blue,Humpback,Beluga'
stringArray.sort(); // ['Beluga', 'Blue', 'Humpback']
numberArray.join(); // '40,1,5,200'
numberArray.sort(); // [1, 200, 40, 5]
numberArray.sort(compareNumbers); // [1, 5, 40, 200]
numericStringArray.join(); // '80,9,700'
numericStringArray.sort(); // ['700', '80', '9']
numericStringArray.sort(compareNumbers); // ['9', '80', '700']
mixedNumericArray.join(); // '80,9,700,40,1,5,200'
mixedNumericArray.sort(); // [1, 200, 40, 5, '700', '80', '9']
mixedNumericArray.sort(compareNumbers); // [1, 5, '9', 40, '80', 200, '700']
排序对象数组
可以通过比较其某个属性的值来对对象数组进行排序。
const items = [
{ name: "Edward", value: 21 },
{ name: "Sharpe", value: 37 },
{ name: "And", value: 45 },
{ name: "The", value: -12 },
{ name: "Magnetic", value: 13 },
{ name: "Zeros", value: 37 },
];
// sort by value
items.sort((a, b) => a.value - b.value);
// sort by name
items.sort((a, b) => {
const nameA = a.name.toUpperCase(); // ignore upper and lowercase
const nameB = b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
排序非ASCII字符
对于排序包含非-ASCII字符的字符串,例如包含重音字符(e、é、è、a、ä等)或来自英语以外其他语言的字符串,请使用String.prototype.localeCompare()
。此函数可以比较这些字符,使它们按正确的顺序显示。
const items = ["réservé", "premier", "communiqué", "café", "adieu", "éclair"];
items.sort((a, b) => a.localeCompare(b));
// items is ['adieu', 'café', 'communiqué', 'éclair', 'premier', 'réservé']
使用map进行排序
compareFn
可以在数组中的每个元素上被调用多次。根据compareFn
的特性,这可能会产生较高的开销。compareFn
的工作量越大,需要排序的元素越多,使用map()
进行排序可能会更高效。其思路是遍历一次数组,将用于排序的实际值提取到一个临时数组中,对临时数组进行排序,然后遍历临时数组以获得正确的顺序。
// the array to be sorted
const data = ["delta", "alpha", "charlie", "bravo"];
// temporary array holds objects with position and sort-value
const mapped = data.map((v, i) => {
return { i, value: someSlowOperation(v) };
});
// sorting the mapped array containing the reduced values
mapped.sort((a, b) => {
if (a.value > b.value) {
return 1;
}
if (a.value < b.value) {
return -1;
}
return 0;
});
const result = mapped.map((v) => data[v.i]);
有一个名为mapsort的开源库使用了这种方法。
sort() 返回对同一数组的引用
sort()
方法返回对原始数组的引用,因此修改返回的数组也会修改原始数组。
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.sort((a, b) => a - b);
// numbers and sorted are both [1, 1, 3, 4, 5]
sorted[0] = 10;
console.log(numbers[0]); // 10
如果您希望sort()
不修改原始数组,而是像其他数组方法(例如map()
)那样返回一个浅拷贝数组,请使用toSorted()
方法。或者,您可以在调用sort()
之前进行浅拷贝,使用扩展语法或Array.from()
。
const numbers = [3, 1, 4, 1, 5];
// [...numbers] creates a shallow copy, so sort() does not mutate the original
const sorted = [...numbers].sort((a, b) => a - b);
sorted[0] = 10;
console.log(numbers[0]); // 3
排序稳定性
从版本10(或ECMAScript 2019)开始,规范规定Array.prototype.sort
是稳定的。
例如,假设您有一个学生列表以及他们的成绩。请注意,学生列表已经按姓名以字母顺序预先排序。
const students = [
{ name: "Alex", grade: 15 },
{ name: "Devlin", grade: 15 },
{ name: "Eagle", grade: 13 },
{ name: "Sam", grade: 14 },
];
按升序对该数组中的grade
进行排序后
students.sort((firstItem, secondItem) => firstItem.grade - secondItem.grade);
students
变量将具有以下值
[
{ name: "Eagle", grade: 13 },
{ name: "Sam", grade: 14 },
{ name: "Alex", grade: 15 }, // original maintained for similar grade (stable sorting)
{ name: "Devlin", grade: 15 }, // original maintained for similar grade (stable sorting)
];
需要注意的是,具有相同成绩的学生(例如,Alex和Devlin)将保持与调用排序之前的相同顺序。这就是稳定的排序算法保证的。
在版本10(或ECMAScript 2019)之前,排序稳定性没有得到保证,这意味着您最终可能会得到以下结果
[
{ name: "Eagle", grade: 13 },
{ name: "Sam", grade: 14 },
{ name: "Devlin", grade: 15 }, // original order not maintained
{ name: "Alex", grade: 15 }, // original order not maintained
];
使用非良构比较器进行排序
如果比较函数不满足所有纯度、稳定性、自反性、反对称性和传递性规则(如描述中所述),则程序的行为是不确定的。
例如,考虑以下代码
const arr = [3, 1, 4, 1, 5, 9];
const compareFn = (a, b) => (a > b ? 1 : 0);
arr.sort(compareFn);
此处的compareFn
函数不是良构的,因为它不满足反对称性:如果a > b
,它返回1
;但是通过交换a
和b
,它返回0
而不是负值。因此,不同引擎的结果数组将不同。例如,V8(Chrome、Node.js等使用)和JavaScriptCore(Safari使用)根本不会对数组进行排序并返回[3, 1, 4, 1, 5, 9]
,而SpiderMonkey(Firefox使用)将按升序对数组进行排序,结果为[1, 1, 3, 4, 5, 9]
。
但是,如果稍微修改compareFn
函数使其返回-1
或0
const arr = [3, 1, 4, 1, 5, 9];
const compareFn = (a, b) => (a > b ? -1 : 0);
arr.sort(compareFn);
那么V8和JavaScriptCore会按降序对其进行排序,结果为[9, 5, 4, 3, 1, 1]
,而SpiderMonkey则按原样返回:[3, 1, 4, 1, 5, 9]
。
由于这种实现不一致性,始终建议您通过遵循五个约束条件使您的比较器良构。
在稀疏数组上使用sort()
空槽将移动到数组的末尾。
console.log(["a", "c", , "b"].sort()); // ['a', 'b', 'c', empty]
console.log([, undefined, "a", "b"].sort()); // ["a", "b", undefined, empty]
在非数组对象上调用sort()
sort()
方法读取this
的length
属性。然后它收集0
到length - 1
范围内所有现有的整数键属性,对其进行排序,并将其写回。如果该范围内缺少属性,则相应的尾随属性将被删除,就像不存在的属性已排序到末尾一样。
const arrayLike = {
length: 3,
unrelated: "foo",
0: 5,
2: 4,
};
console.log(Array.prototype.sort.call(arrayLike));
// { '0': 4, '1': 5, length: 3, unrelated: 'foo' }
规范
规范 |
---|
ECMAScript语言规范 # sec-array.prototype.sort |
浏览器兼容性
BCD表格仅在浏览器中加载