[译][undefined, null, NaN].sort();

·414 Views·

数组排序是我们不会花长时间考虑的事情之一,直到它停止为我们工作。最近我就使用 javascript 的数组,对一组数据进行排序,但排序结果完全错乱。我花了好长时间才确定哪里出现了问题。

所以,想和大家分享发生了什么以及为什么它的结果如此奇怪。

基本排序

数组有个 sort 排序方法,使用它,会得出我们所预料的排序结果。举个例子:

1const stringArray = ['cat', 'dog', 'ant', 'butterfly'];
2stringArray.sort();
3// => [ 'ant', 'butterfly', 'cat', 'dog' ]


如果在排序对象中包含 undefined ,也会正确返回结果。MDN 也介绍说,所有的 undefined 对象会放到数组中的结尾

1const stringArrayWithUndefined = [
2 'cat',
3 undefined,
4 'dog',
5 undefined,
6 'ant',
7 'butterfly',
8 'zebra'
9];
10stringArrayWithUndefined.sort();
11// => [ 'ant', 'butterfly', 'cat', 'dog', 'zebra', undefined, undefined ]


陷进

第一个问题,你可能会在一个数组中包含一个 null 。

1const stringArrayWithUndefinedAndNull = [
2 'cat',
3 undefined,
4 'dog',
5 undefined,
6 'ant',
7 null,
8 'butterfly',
9 'zebra'
10];
11stringArrayWithUndefinedAndNull.sort();
12// => [ 'ant', 'butterfly', 'cat', 'dog', null, 'zebra', undefined, undefined ]

排序将会把 null 强制转换为字符串的 “null”,所以它出现在以字母为排序的一个位置,比如在 ’zebra‘ 前面,因为 n 在 z 前面。

接下来,这里有个只包含数字的数组。默认的排序算法是把数字全部转换为字符串,然后根据他们在 utf-16 中代码顺序进行排序。

刚才已经看到它会正确排序字符串。但它会把数字排成错误的结果。

1const numberArray = [5, 3, 7, 1];
2numberArray.sort();
3// => [ 1, 3, 5, 7 ]
4const biggerNumberArray = [5, 3, 10, 7, 1];
5biggerNumberArray.sort();
6// => [ 1, 10, 3, 5, 7 ]

可以看到,10 数字排到了 3 的前面,因为字符串 “10” 在字符串 “3”的前面。

我们可以给 sort 函数,传递一个比较函数修复这个问题。比较函数接受两个值,并返回一个大于 0 ,等于 0 或者小于 0 的数字。如果小于0 ,第一个数字在第二个数字的前面。如果大于0 ,第二个参数数字在第一个参数数字的前面。如果等于0,这两个数字则呆在他们原来的位置上。

升序排列一组数字,则传递下面的比较函数

1const compareNumbers = (a, b) => a - b;

上面提到的 biggerNumberArray 数组,使用这个比较函数,会返回正确的排序结果。

1biggerNumberArray.sort(compareNumbers);
2// => [ 1, 3, 5, 7, 10 ]

如果所要排序的数组中,含有 undefined 元素,比较函数也会忽略它,并把它放到排序结果的后面。

1const numberArrayWithUndefined = [5, undefined, 3, 10, 7, 1];
2numberArrayWithUndefined.sort(compareNumbers);
3// => [ 1, 3, 5, 7, 10, undefined ]

传递 null ,又会产生问题。

1const numberArrayWithUndefinedAndNull = [5, undefined, 3, null, 10, 7, 1];
2numberArrayWithUndefinedAndNull.sort(compareNumbers);
3// => [ null, 1, 3, 5, 7, 10, undefined ]


产生这种情况,是因为强制把 null 转换成数字 0 了 。

1Number(null);
2// => 0

可以在 compareNumbers 中处理这种情况或者明确知道这个问题。

真实的案例

最近遇到的最大问题,是 undefined 以另一种方式出现在真实的代码中。正如我们刚刚所看到,如果一个数组中含有 undefined ,它会忽略(不会调用比较函数)并放到数组的结尾。

但如果所要排序数组中是一系列对象,当某一个对象为空对象 ,那最终排序的结果会不稳定。

举个列子,数组中某个对象没有 value ,试着去排序,结果会不符合你的预期。

1const objectArray = [
2 { value: 1 },
3 { value: 10 },
4 {},
5 { value: 5 },
6 { value: 7 },
7 { value: 3 }
8];
9const compareObjects = (a, b) => a.value - b.value;
10objectArray.sort(compareObjects);
11// => [ { value: 1 },
12// { value: 10 },
13// {},
14// { value: 3 },
15// { value: 5 },
16// { value: 7 } ]
17

数字和 undefined 相减或者 undefined 和 数字相减,会返回 NaN ,在比较函数中返回的并不是比较函数所需要的值,结果就比较奇怪。

在这个例子中,这个空对象,就会呆在原来的位置上,剩余的将会正常排序。

有一些方案可以解决,但重要的是知道会发生这种情况。

在我的案例中,先过滤掉那些值为 undefined 的对象,然后在进行正常排序。

1objectArray
2 .filter(obj => typeof obj.value !== 'undefined')
3 .sort(compareObjects);
4// => [ { value: 1 },
5// { value: 3 },
6// { value: 5 },
7// { value: 7 },
8// { value: 10 } ]


结语

以上这些可以看出排序方法并不是想象的那么简单。字符串能能很好的运行,数字需要一些输入和输出。

但如果在对象数组中碰到 null 或者 undefined ,我们一定要睁大眼睛时刻保持注意。

原文:https://hackernoon.com/how-not-to-sort-an-array-in-javascript-5t2a32av4