• 导航

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

前端杂烩 2024-04-05 20 次浏览
数组排序是我们不会花长时间考虑的事情之一,直到它停止为我们工作。最近我就使用 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 ,我们一定要睁大眼睛时刻保持注意。