为了帮助阐明其行为Array#sort
及其比较器,请考虑在开始编程课程中教授的这种幼稚的插入排序:
const sort = arr => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && arr[j-1] > arr[j]; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array);
console.log("" + array);
忽略插入排序作为算法的选择,专注于硬编码比较器:arr[j-1] > arr[j]
. 这有两个与讨论相关的问题:
- 该
>
运算符在数组元素对上调用,但您可能想要排序的许多事情(例如对象)没有以>
合理的方式响应(如果我们使用 也是如此-
)。
- 即使您正在处理数字,通常您也需要一些其他的排列方式,而不是这里的升序排列。
comparefn
我们可以通过添加您熟悉的参数来解决这些问题:
const sort = (arr, comparefn) => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array, (a, b) => a - b);
console.log("" + array);
sort(array, (a, b) => b - a);
console.log("" + array);
const objArray = [{id: "c"}, {id: "a"}, {id: "d"}, {id: "b"}];
sort(objArray, (a, b) => a.id.localeCompare(b.id));
console.log(JSON.stringify(objArray, null, 2));
现在,朴素排序例程被推广。您可以确切地看到调用此回调的时间,回答您的第一组问题:
排序过程中是否多次调用数组排序回调函数?如果是这样,我想知道每次将哪两个数字传递给函数
运行下面的代码表明,是的,该函数被多次调用,您可以使用console.log
它来查看传入了哪些数字:
const sort = (arr, comparefn) => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
console.log("on our version:");
const array = [3, 0, 4, 5];
sort(array, (a, b) => console.log(a, b) || (a - b));
console.log("" + array);
console.log("on the builtin:");
console.log("" +
[3, 0, 4, 5].sort((a, b) => console.log(a, b) || (a - b))
);
你问:
那么这两组数字是如何相互排序的呢?
准确地说,a
不是b
数字集——它们是数组中的对象(在您的示例中,它们是数字)。
事实是,它们如何排序并不重要,因为它依赖于实现。如果我使用了与插入排序不同的排序算法,比较器可能会在不同的数字对上调用,但在排序调用结束时,对 JS 程序员重要的不变量是结果数组是根据排序的比较器,假设比较器返回符合您声明的合同的值(< 0 when a < b
, 0 whena === b
和 > 0 when a > b
)。
同样,只要我不违反规范,我就可以自由更改排序的实现,ECMAScript 的实现可以在语言规范的范围内自由选择排序实现,因此Array#sort
可能会产生不同的比较器调用在不同的引擎上。一个人不会编写逻辑依赖于某些特定比较序列的代码(比较器也不应该首先产生副作用)。
例如,V8 引擎(在撰写本文时)在数组大于某个预先计算的元素数量时调用Timsort ,并对小数组块使用二进制插入排序。但是,它曾经使用不稳定的快速排序,并且可能会给出不同的参数序列和对比较器的调用。
由于不同的排序实现以不同的方式使用比较器函数的返回值,因此当比较器不遵守约定时,这可能会导致令人惊讶的行为。有关示例,请参见此线程。