ES11 中的 Array.prototype.sort 排序方法的技巧和注意事项

Array.prototype.sort() 是 JavaScript 中一个十分常用的数组排序方法。但是这个方法在浏览器和 Node.js 中的排序效果可能会有所差异,在 ES11 中也加入了一些新的特性,因此需要我们注意一些技巧和注意事项以便更好地使用这个方法。本篇文章将会对这些细节进行详尽的讲解和示例代码演示。

基础使用

sort() 方法的基础用法很简单,它可以在原地对数组进行排序,即修改原数组而不返回新的数组。排序方法默认是按照 Unicode 码点进行排序的,如果希望按照数字大小,需要传入一个比较函数。

示例如下:

const arr = [3, 15, 2, 8, 1];
// 按照数字从小到大排序
arr.sort((a, b) => a - b);
console.log(arr);
// [1, 2, 3, 8, 15]

以上代码中,我们按照数字从小到大对数组进行排序,输出的结果为 [1, 2, 3, 8, 15]。需要注意的是,sort() 方法会直接修改原数组,因此需要注意是否需要保留原数组。

注意事项

  1. 排序可能会改变元素顺序,如果需要保留元素顺序,需要通过浅拷贝或深拷贝来进行排序。
  2. 比较函数需要比较的是排序关键字,而不是对应的数组下标或值。
  3. 比较函数需要是稳定的,即对于相等的元素,排序后它们的顺序不会改变。

接下来,我们将通过示例代码来一一介绍这些注意事项。

1. 排序可能会改变元素顺序

示例代码如下:

const arr = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
];

// 按照 name 字段排序
arr.sort((a, b) => a.name.localeCompare(b.name));
console.log(arr);
// 输出:[ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]

以上代码中,我们按照 name 字段排序,但是输出的结果与期望的不一样,原因是 sort() 方法直接改变了原始数组。我们可以通过浅拷贝来避免这个问题:

const arr = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
];

// 按照 name 字段排序
const newArr = [...arr].sort((a, b) => a.name.localeCompare(b.name));
console.log(arr);
// 输出:[ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]
console.log(newArr);
// 输出:[ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]

以上代码中,我们通过浅拷贝创建了一个新数组 newArr,并对其进行排序,同时保留了原数组 arr 的顺序。

2. 比较函数需要比较的是排序关键字

示例代码如下:

const arr = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
];

// 错误的比较函数
const compareFunc = (a, b) => a.name - b.name;
arr.sort(compareFunc);
console.log(arr);
// 输出:[ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]

以上代码中,我们定义了一个错误的比较函数,试图按照 name 字段排序,但是输出的结果却没有进行排序。原因是比较函数应该是一个用来比较排序关键字,而不是对应的数组下标或值。我们需要改用如下的比较函数:

const compareFunc = (a, b) => a.name.localeCompare(b.name);
arr.sort(compareFunc);
console.log(arr);
// 输出:[ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]

以上代码中,我们通过 localeCompare() 方法来比较字符串大小,实现了按照 name 字段排序。

3. 比较函数需要是稳定的

稳定排序是指在排序后,对于相等的元素,它们的顺序不会改变。如果不保证稳定性,排序后可能得到不同的结果,这对于一些需要排序的应用场景来说是无法接受的。在 JavaScript 中,sort() 方法默认是不稳定的。

示例代码如下:

const arr = [
  { id: 1, name: 'Bob' },
  { id: 2, name: 'Alice' },
  { id: 3, name: 'Bob' },
];

// 不稳定排序
arr.sort((a, b) => a.name.localeCompare(b.name));
console.log(arr);
// 输出:[ { id: 2, name: 'Alice' }, { id: 1, name: 'Bob' }, { id: 3, name: 'Bob' } ]

以上代码中,我们试图按照 name 字段进行排序,但是得到的结果却是不稳定的。这个问题可以通过以下两种方法解决:

  1. 添加额外的排序关键字,比如按照 id 字段进行排序:

    arr.sort((a, b) => {
      const cmp = a.name.localeCompare(b.name);
      return cmp === 0 ? a.id - b.id : cmp;
    });
    console.log(arr);
    // 输出:[ { id: 2, name: 'Alice' }, { id: 1, name: 'Bob' }, { id: 3, name: 'Bob' } ]

    以上代码中,我们首先按照 name 字段进行排序,如果 name 字段相同,则按照 id 字段进行排序。这样可以保证排序的稳定性。

  2. 使用稳定排序算法,比如归并排序:

    const mergeSort = (arr, compareFunc) => {
      const merge = (left, right) => {
        let result = [];
        let l = 0;
        let r = 0;
        while (l < left.length && r < right.length) {
          if (compareFunc(left[l], right[r]) <= 0) {
            result.push(left[l]);
            l++;
          } else {
            result.push(right[r]);
            r++;
          }
        }
        return result.concat(left.slice(l)).concat(right.slice(r));
      };
      const sort = (arr) => {
        if (arr.length <= 1) {
          return arr;
        }
        const mid = Math.floor(arr.length / 2);
        const left = arr.slice(0, mid);
        const right = arr.slice(mid);
        return merge(sort(left), sort(right));
      };
      return sort([...arr]);
    };
    
    arr = mergeSort(arr, (a, b) => a.name.localeCompare(b.name));
    console.log(arr);
    // 输出:[ { id: 2, name: 'Alice' }, { id: 1, name: 'Bob' }, { id: 3, name: 'Bob' } ]

    以上代码中,我们实现了一个归并排序算法,并将其应用到数组排序中,可以得到稳定的排序结果。

ES11 的新特性

在 ES11 中,sort() 方法添加了返回完整排序结果的功能。我们可以通过 Array.prototype.flatMap() 方法来实现这个功能。

示例代码如下:

const arr = [
  { id: 1, name: 'Bob' },
  { id: 2, name: 'Alice' },
  { id: 3, name: 'Charlie' },
];

// 新特性
const newSort = (arr, compareFunc) => arr.flatMap((value) => value).sort(compareFunc);
console.log(newSort(arr, (a, b) => a.name.localeCompare(b.name)));
// 输出:[ { id: 2, name: 'Alice' }, { id: 1, name: 'Bob' }, { id: 3, name: 'Charlie' } ]

以上代码中,我们定义了一个 newSort() 方法,利用了 Array.prototype.flatMap() 方法的特性,返回了完整排序后的结果。

总结

Array.prototype.sort() 是 JavaScript 中的一个常用数组排序方法,但是在使用中需要注意排序对原始数组的影响、比较函数需要比较的是排序关键字、比较函数需要是稳定的等一系列问题。在 ES11 中,sort() 方法添加了新的返回完整排序结果的功能,可以更方便地使用。在实际应用中,我们需要根据具体场景选择不同的排序算法,并注意使用时的注意事项。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a24de3add4f0e0ffa6980f


纠错反馈