[转载或参考] https://blog.youkuaiyun.com/github_39319000/article/details/89554079
[转载或参考]https://blog.youkuaiyun.com/weixin_33862188/article/details/88679236
【仅供自己学习查询, 侵删】
关于 sort () 比较 遇到的坑,浅析 【附带:源码】
1、 不传值(默认)
示例:
var arr = [1, 5, 11, 10]
arr.sort() // [1, 10, 11, 5]
这里并没有得到我们想要的 [1, 5 ,10, 11]
原因:
- 默认情况,sort 是升序
- sort 默认会将 元素转化成 string 类型 , 因此这儿的 Number 类型比较的其实是 String 类型。
回顾下: 字符串的比较:
abc
abde
"c".charCodeAt() // 99 "d".charCodeAt() // 100 a-z 97-122 A-Z 65-90 0-9 45-57
字符串大小比较是 比较相同index字符的 ASII码, c (99) < d (100)
因此 abc < abd
回顾问题: "5" 和 "11", 比较第一位, "1" 为46, "5"为 50, 故 "1"<"5", 比较结束, "11"<"5",
得出: [1, 10, 11, 5]
2、 函数返回值 bool 类型 (坑)
var arr = [1, 10, 5, 11] // 目标值: [1, 5, 10, 11]
// 返回 bool 值
arr.sort((x,y)=> x>y ) // [1, 10, 5, 11] 未生效
// 返回 Number 值
arr.sort((x,y)=> x-y ) // [1, 5, 10, 11] 生效
原因: 当传入回调函数时候,
返回 bool 值,不生效
返回 Bumber 值时候, 生效。 转化成的 true 和 false 并不会转化成数值,因此不生效。
原因是因为,sort ( fn ) 会调用 fn ,最后根据 数字 进行区分,
- 当等于0时, 不移动
- 等大于0时, 交换
- 当小于0时,交换
3、总结:
- 不传值,默认升序,会转化成 字符串进行比较
- 有fn传值,根据 return 的 Number 值进行比较,注意 retrun 布尔类型值。函数不生效。
3、数据元素少的时候,采用的是插入排序, 元素多的时候,采用的是快排。
4、V8引擎源码
function ArraySort(comparefn) {
// In-place QuickSort algorithm.
// For short (length <= 22) arrays, insertion sort is used for efficiency.
var custom_compare = IS_FUNCTION(comparefn);
function Compare(x,y) {
// Assume the comparefn, if any, is a consistent comparison function.
// If it isn't, we are allowed arbitrary behavior by ECMA 15.4.4.11.
if (x === y) return 0;
if (custom_compare) {
// Don't call directly to avoid exposing the builtin's global object.
return comparefn.call(null, x, y);
}
if (%_IsSmi(x) && %_IsSmi(y)) {
return %SmiLexicographicCompare(x, y);
}
x = ToString(x);
y = ToString(y);
if (x == y) return 0;
else return x < y ? -1 : 1;
};
function InsertionSort(a, from, to) {
for (var i = from + 1; i < to; i++) {
var element = a[i];
// Pre-convert the element to a string for comparison if we know
// it will happen on each compare anyway.
var key =
(custom_compare || %_IsSmi(element)) ? element : ToString(element);
// place element in a[from..i[
// binary search
var min = from;
var max = i;
// The search interval is a[min..max[
while (min < max) {
var mid = min + ((max - min) >> 1);
var order = Compare(a[mid], key);
if (order == 0) {
min = max = mid;
break;
}
if (order < 0) {
min = mid + 1;
} else {
max = mid;
}
}
// place element at position min==max.
for (var j = i; j > min; j--) {
a[j] = a[j - 1];
}
a[min] = element;
}
}
function QuickSort(a, from, to) {
// Insertion sort is faster for short arrays.
if (to - from <= 22) {
InsertionSort(a, from, to);
return;
}
var pivot_index = $floor($random() * (to - from)) + from;
var pivot = a[pivot_index];
// Pre-convert the element to a string for comparison if we know
// it will happen on each compare anyway.
var pivot_key =
(custom_compare || %_IsSmi(pivot)) ? pivot : ToString(pivot);
// Issue 95: Keep the pivot element out of the comparisons to avoid
// infinite recursion if comparefn(pivot, pivot) != 0.
a[pivot_index] = a[from];
a[from] = pivot;
var low_end = from; // Upper bound of the elements lower than pivot.
var high_start = to; // Lower bound of the elements greater than pivot.
// From low_end to i are elements equal to pivot.
// From i to high_start are elements that haven't been compared yet.
for (var i = from + 1; i < high_start; ) {
var element = a[i];
var order = Compare(element, pivot_key);
if (order < 0) {
a[i] = a[low_end];
a[low_end] = element;
i++;
low_end++;
} else if (order > 0) {
high_start--;
a[i] = a[high_start];
a[high_start] = element;
} else { // order == 0
i++;
}
}
QuickSort(a, from, low_end);
QuickSort(a, high_start, to);
}
var old_length = ToUint32(this.length);
if (old_length < 2) return this;
%RemoveArrayHoles(this);
var length = ToUint32(this.length);
// Move undefined elements to the end of the array.
for (var i = 0; i < length; ) {
if (IS_UNDEFINED(this[i])) {
length--;
this[i] = this[length];
this[length] = void 0;
} else {
i++;
}
}
QuickSort(this, 0, length);
// We only changed the length of the this object (in
// RemoveArrayHoles) if it was an array. We are not allowed to set
// the length of the this object if it is not an array because this
// might introduce a new length property.
if (IS_ARRAY(this)) {
this.length = old_length;
}
return this;
}