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

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

基本排序

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

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

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

const stringArrayWithUndefined = [
  'cat',
  undefined,
  'dog',
  undefined,
  'ant',
  'butterfly',
  'zebra'
];
stringArrayWithUndefined.sort();
// => [ 'ant', 'butterfly', 'cat', 'dog', 'zebra', undefined, undefined ]

陷进

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

const stringArrayWithUndefinedAndNull = [
  'cat',
  undefined,
  'dog',
  undefined,
  'ant',
  null,
  'butterfly',
  'zebra'
];
stringArrayWithUndefinedAndNull.sort();
// => [ 'ant', 'butterfly', 'cat', 'dog', null, 'zebra', undefined, undefined ]

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

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

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

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

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

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

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

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

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

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

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

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

传递 null ,又会产生问题。

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

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

Number(null);
// => 0

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

真实的案例

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

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

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

const objectArray = [
  { value: 1 },
  { value: 10 },
  {},
  { value: 5 },
  { value: 7 },
  { value: 3 }
];
const compareObjects = (a, b) => a.value - b.value;
objectArray.sort(compareObjects);
// => [ { value: 1 },
//      { value: 10 },
//      {},
//      { value: 3 },
//      { value: 5 },
//      { value: 7 } ]

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

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

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

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

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

结语

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

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

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