你是不是对sort有什么误解?

471 阅读4分钟

sort() 方法对数组的元素做原地的排序,并返回这个数组。 sort 排序可能是不稳定的。默认按照字符串的Unicode码位点(code point)排序。如果sort处理的数组元素中有undefined,那么所有undefined元素都排序到数组的末尾。

比较常用的unicode排序,数字字符 < 大写字符 < 小写字符;

我们知道在js中数字和字符串是两种不同的数据类型,是没有办法直接进行比较,因此在调用sort方法的时候会对数字进行字符串的转换即 数字.toString() = 字符串,其实无论是Number型还是其它类型,sort方法在进行两元素之间的比较时会转化为字符型后再进行比较,但并不改变原数组内的元素。这样就可以根据unicode表格来进行排序了

语法

arr.sort([compareFunction])

参数

compareFunction是可选参数。用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的诸个字符的Unicode位点依次进行排序。

没有传递compareFunction参数:

如果没有指明 compareFunction参数 ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序的意思为:

  • 例如排序字符串时,由于'B'的编码小于'c'的unicode编码,“Banana” 会被排列到 “cherry” 之前。
  • 比较数字大小时,9应该要排在80的前面,但使用sort方法比较时数字会先被转换为字符串,'8'的unicode编码小于'9'的,所以 “80” 会 排在“9”的前面 。
var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); // ['apples', 'bananas', 'cherries']

如果两个数字字符的第一个字符unicode的编码一样,那么就会比较第二个的,以此类推。

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。记 a 和 b 是两个将要被比较的元素:

  • 如果该函数的返回值小于 0 ,那么 a 会被排列到 b 之前
  • 如果该函数的返回值等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本)
  • 如果该函数的返回值大于 0 , a 将会被排列到b后面

compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

我们传递的函数所sort方法内部帮助我们调用的,每次调用都会传递两个值进去比较,所以参数a、b就是此次sort需要比较的值

如果我们希望数组是升序排序的,那么可以将函数的返回值设置为a - b,这样大的值就会排在后面,比较函数格式如下:

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return a - b;
});
console.log(numbers); // [1, 2, 3, 4, 5] 注意:sort方法是会改变原数组

简单解释一下:

因为sort的机制就是根据函数的返回值决定第一个参数和第二个参数排列的顺序,如果函数返回的结果大于0,那么第一个参数a就会排列在b后面。利用这个性质,只需要将返回值设置为a-b就能够达到升序的效果;而如果我们将返回值设置为第二个参数b-第一个参数a,那么大的那一个数字就会排列到前面,这样能达到降序的效果

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return b - a;
});
console.log(numbers); // [5, 4, 3, 2, 1]

Array.prototype.sort 各浏览器的算法实现

ECMAScript 不同版本规范对 Array.prototype.sort 的定义中没有要求用什么样的排序方式实现 sort() 方法,也没有要求是否要采用稳定算法。因此各浏览器都给出自己的实现方式:

浏览器使用的 JavaScript 引擎排序算法源码地址
Google ChromeV8插入排序和快速排序sort 源码实现
Mozilla FirefoxSpiderMonkey归并排序sort 源码实现
SafariNitro(JavaScriptCore )归并排序和桶排序sort 源码实现
Microsoft Edge 和 IE(9+)Chakra快速排序sort 源码实现

源码分析

V8引擎的一段注释:

// In-place QuickSort algorithm. 
// For short (length <= 10) arrays, insertion sort is used for efficiency.
  • Google Chrome 对 sort 做了特殊处理,对于长度 <= 10 的数组使用的是插入排序(稳定排序算法) ,长度>10 的数组使用的是快速排序。快速排序是不稳定的排序算法。
  • safari默认使用的桶排序,如果 sort 传入的自定义函数作为参数,就是用归并排序(稳定排序算法);
  • Firefox使用的是稳定排序算法--归并排序法;
  • Microsoft Edge 和 IE(9+) 使用的不稳定排序算法 - 快速排序。

不过上述浏览器所用到的排序肯定是比普通算法要复杂的多。比如V8引擎,虽然其源代码较为复杂,不过核心算法还是快速排序。算法复杂的原因在于v8出于性能考虑进行了很多优化。

排序类型平均情况最好情况最坏情况辅助空间稳定性
快速排序O(nlogn)O(nlogn)O(n²)O(nlogn)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
插入排序O(n²)O(n)O(n²)O(1)稳定
桶排序O(n+k)O(n+k)O(n²)O(n+k)(不)稳定

常用的时间复杂度所耗费的时间从小到大依次是

O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^n) < O(n!) < O(n^n)

解决办法

Array.prototype.sort 在不同浏览器中的差异和解决办法

大体的思路就是,自己写一个稳定的排序函数,以保持各浏览器的一致性。

参考文章:hufangyun.com/2017/sort-a…