Chrome的bug?
在JavaScript里排序,我们一般都会用Array类的sort方法,例如:
1 2 3 |
|
没问题,那我们再看一个例子:
1 2 3 4 |
|
Firefox和IE下结果正确,但是在chrome下结果大跌眼镜,4跑到最前面去了,这难道是chrome的bug吗?
V8排序的算法
于是我翻出V8引擎对数组排序实现的代码,研究了一番
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
|
V8的实现是当数组长度小于10的时候采用插入排序,否则使用快速排序。我们刚才的第二个例子的数组长度为12,所以应该走快速排序的逻辑,而大家知道快速排序算法的思想就是找到一个轴,将大于和小于它的数据分居在轴两侧,然后递归这一动作直到结束。
那么我们可以用例子二的数据模拟下V8算法的流程:
- 首先找到v0,v1,v2三个位置(头,尾,正中间)
- 用comparefn判断v0(头)和v1(尾)两个位置的数据是否应该交换,我们传入的comparefn是返回
a < b
,那么5 < 5
应该返回false(即c01),而上面代码的第40行if (c01 > 0)
的判断语句应该为false,所以无需交换头尾位置; - 再用comparefn判断v0(头)和v2(正中)两个位置是否应该交换,那么
5 < 4
返回false(即c02),那么根据47行的条件判断if (c02 >= 0)
(c02隐式转换为0,所以0 >= 0为真),应该交换v0和v2,到这里我们已经可以看出为什么降序排序最小的4反而跑到最前面去了。(后面的流程略过)
那么这是v8或者chrome的bug吗?我google了下,确实不少人提到了这个issue。根据上面的回复以及MDN上对于compareFn的定义,严格来说v8实现的没有问题,问题在于我的comparefn返回的是一个布尔值(true or false),而ECMAScript里期待comparefn返回的是-1,0和1三种数值,这里true转换成了1,false转换成了0,而对于0这个值的处理ECMAScript里没有做明确的规定,所以导致各个浏览器实现的不一致。
那么我们把comparefn替换成标准的返回-1,0和1的函数,再看看结果
1 2 3 4 |
|
OK,完全正确,所以说在写comparefn时一定要对三种情况分别返回-1,0和1,否则就会出现上面例子2的莫名其妙的问题。