JavaScript 原生 sort() 方法详解

JavaScript 原生 sort() 方法详解

一、基本语法

javascript

javascript 复制代码
// 语法
arr.sort([compareFunction])

// 返回值:排序后的原数组(原地修改)
const sortedArray = arr.sort(compareFunction);

二、默认行为(不使用比较函数)

1. 字符串排序(默认)

javascript

javascript 复制代码
const fruits = ['banana', 'apple', 'cherry', 'date'];
fruits.sort();
console.log(fruits);  // ['apple', 'banana', 'cherry', 'date']

const numbersAsStrings = ['10', '2', '1', '20'];
numbersAsStrings.sort();
console.log(numbersAsStrings);  // ['1', '10', '2', '20'] ⚠️ 按字符串排序

2. 数字排序问题

javascript

javascript 复制代码
const numbers = [10, 2, 1, 20];
numbers.sort();
console.log(numbers);  // [1, 10, 2, 20] ❌ 不是数字顺序!

// 这是因为 sort() 默认将元素转换为字符串比较
// 比较过程:"10" < "2"  // true(按字典序比较)

三、比较函数详解

1. 基本数字排序

javascript

javascript 复制代码
// 升序排序
arr.sort((a, b) => a - b);

// 降序排序
arr.sort((a, b) => b - a);

// 完整示例
const numbers = [40, 100, 1, 5, 25, 10];

numbers.sort((a, b) => a - b);
console.log(numbers);  // [1, 5, 10, 25, 40, 100]

numbers.sort((a, b) => b - a);
console.log(numbers);  // [100, 40, 25, 10, 5, 1]

2. 比较函数工作原理

javascript

javascript 复制代码
// 比较函数应该返回:
// - 负数:a 排在 b 前面
// - 零:a 和 b 相对位置不变
// - 正数:a 排在 b 后面

function compare(a, b) {
    if (a < b) {
        return -1;  // a 在前
    }
    if (a > b) {
        return 1;   // a 在后
    }
    return 0;       // 位置不变
}

// 简化版(仅适用于数字)
function compareNumbers(a, b) {
    return a - b;  // 升序
}

四、V8 引擎实现原理

1. 使用的算法:Timsort

javascript

javascript 复制代码
// Timsort = Tim Peters' sort = 归并排序 + 插入排序优化
// 特点:
// 1. 稳定排序(相等元素保持原顺序)
// 2. 自适应(根据数据特征选择策略)
// 3. 时间复杂度:O(n log n)
// 4. 空间复杂度:O(n)

// 工作原理:
// 1. 寻找"run"(已经有序的片段)
// 2. 小片段使用插入排序
// 3. 使用归并排序合并片段
// 4. 优化内存使用(原地归并或临时数组)

2. 性能特征

javascript

javascript 复制代码
// 不同数据规模的性能表现
const dataSizes = [100, 1000, 10000, 100000, 1000000];

// Timsort 对以下情况特别优化:
// 1. 部分有序数组
// 2. 包含重复元素的数组
// 3. 逆序数组
// 4. 随机数组

// 在 Chrome 中的实际表现:
// - 小数组(< 10):插入排序
// - 中等数组:快速排序变体
// - 大数组:Timsort

五、复杂对象排序

1. 按对象属性排序

javascript

javascript 复制代码
const users = [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 25 },
    { name: 'Charlie', age: 35 }
];

// 按年龄升序
users.sort((a, b) => a.age - b.age);

// 按名字字母顺序
users.sort((a, b) => a.name.localeCompare(b.name));

// 多条件排序
users.sort((a, b) => {
    if (a.age !== b.age) {
        return a.age - b.age;  // 先按年龄
    }
    return a.name.localeCompare(b.name);  // 年龄相同按名字
});

2. 自定义排序规则

javascript

javascript 复制代码
const priorities = ['high', 'medium', 'low'];
const tasks = [
    { title: 'Task A', priority: 'medium' },
    { title: 'Task B', priority: 'high' },
    { title: 'Task C', priority: 'low' }
];

tasks.sort((a, b) => {
    return priorities.indexOf(a.priority) - priorities.indexOf(b.priority);
});
// 结果:high -> medium -> low

六、特殊排序需求

1. 中文排序

javascript

javascript 复制代码
const chineseNames = ['张三', '李四', '王五', '赵六'];

// 简单排序(Unicode顺序)
chineseNames.sort();
console.log(chineseNames);  // Unicode 顺序

// 正确的中文拼音排序
chineseNames.sort((a, b) => a.localeCompare(b, 'zh-CN'));
console.log(chineseNames);  // 按拼音排序

// 支持更多选项
chineseNames.sort((a, b) => 
    a.localeCompare(b, 'zh-CN', {
        sensitivity: 'base',  // 忽略大小写和重音
        numeric: true         // 数字作为数字比较
    })
);

2. 带单位的字符串排序

javascript

javascript 复制代码
const sizes = ['10px', '2px', '100px', '50px'];

sizes.sort((a, b) => {
    const numA = parseInt(a);
    const numB = parseInt(b);
    return numA - numB;
});
console.log(sizes);  // ['2px', '10px', '50px', '100px']

3. 日期排序

javascript

javascript 复制代码
const dates = [
    '2024-01-15',
    '2023-12-01',
    '2024-02-20',
    '2023-11-10'
];

dates.sort((a, b) => new Date(a) - new Date(b));
// 或
dates.sort((a, b) => a.localeCompare(b));  // 字符串比较也适用

七、性能优化技巧

1. 缓存比较结果

javascript

javascript 复制代码
// 优化前(每次比较都计算)
const complexObjects = [...]; // 大量复杂对象
complexObjects.sort((a, b) => 
    calculateComplexValue(a) - calculateComplexValue(b)
);

// 优化后(Schwartzian transform)
complexObjects
    .map(obj => ({
        original: obj,
        value: calculateComplexValue(obj)
    }))
    .sort((a, b) => a.value - b.value)
    .map(item => item.original);

2. 避免在比较函数中创建对象

javascript

javascript 复制代码
// 不好:每次比较都创建新对象
arr.sort((a, b) => {
    const dateA = new Date(a.timestamp);
    const dateB = new Date(b.timestamp);
    return dateA - dateB;
});

// 好:预先处理
arr.forEach(item => {
    item._sortDate = new Date(item.timestamp);
});
arr.sort((a, b) => a._sortDate - b._sortDate);
arr.forEach(item => delete item._sortDate);

八、常见陷阱

1. 原地修改问题

javascript

javascript 复制代码
const original = [3, 1, 4, 1, 5];
const sorted = original.sort();

console.log(original);  // [1, 1, 3, 4, 5] ❌ 原数组被修改了!
console.log(sorted);    // [1, 1, 3, 4, 5]  同一个数组

// 正确做法:先复制
const safeSorted = [...original].sort();
// 或
const safeSorted = original.slice().sort();

2. 不稳定的比较函数

javascript

javascript 复制代码
// 错误的比较函数(可能引起问题)
[1, 2, 3, 4, 5].sort(() => Math.random() - 0.5);
// 这不保证均匀分布,也不高效

// 正确洗牌算法(Fisher-Yates)
function shuffle(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

3. NaN 处理

javascript

javascript 复制代码
const arr = [1, NaN, 3, 2, NaN, 4];

arr.sort((a, b) => a - b);
console.log(arr);  
// [1, 2, 3, 4, NaN, NaN]  // NaN 被排到最后

// 自定义 NaN 处理
arr.sort((a, b) => {
    if (isNaN(a) && isNaN(b)) return 0;
    if (isNaN(a)) return 1;  // NaN 在后
    if (isNaN(b)) return -1; // 非NaN在前
    return a - b;
});

九、高级应用

1. 稳定排序模拟

javascript

javascript 复制代码
// 如果需要绝对稳定(旧浏览器),可以添加索引
const data = [{value: 2}, {value: 1}, {value: 2}];

data
    .map((item, index) => ({item, index}))
    .sort((a, b) => {
        const valueDiff = a.item.value - b.item.value;
        if (valueDiff !== 0) return valueDiff;
        return a.index - b.index;  // 保持原顺序
    })
    .map(({item}) => item);

2. 多维度动态排序

javascript

javascript 复制代码
function dynamicSort(properties) {
    return function(a, b) {
        for (const {property, order = 'asc'} of properties) {
            const aVal = a[property];
            const bVal = b[property];
            
            if (aVal < bVal) return order === 'asc' ? -1 : 1;
            if (aVal > bVal) return order === 'asc' ? 1 : -1;
        }
        return 0;
    };
}

const items = [...];
items.sort(dynamicSort([
    {property: 'category', order: 'asc'},
    {property: 'price', order: 'desc'},
    {property: 'name', order: 'asc'}
]));

3. 性能基准测试

javascript

javascript 复制代码
function benchmarkSort(arr, sortFn, iterations = 100) {
    const times = [];
    
    for (let i = 0; i < iterations; i++) {
        const copy = [...arr];
        const start = performance.now();
        sortFn(copy);
        const end = performance.now();
        times.push(end - start);
    }
    
    return {
        avg: times.reduce((a, b) => a + b) / times.length,
        min: Math.min(...times),
        max: Math.max(...times),
        stdDev: Math.sqrt(
            times.map(t => Math.pow(t - avg, 2))
                 .reduce((a, b) => a + b) / times.length
        )
    };
}

十、总结

最佳实践:

  1. 始终提供比较函数(除非明确需要字符串排序)

  2. 注意原地修改 - 必要时先复制数组

  3. 复杂比较预计算 - 提高性能

  4. 利用稳定性 - 多条件排序依赖稳定排序特性

  5. 考虑数据特性 - 部分有序数据 Timsort 表现更好


选择 sort() 当:

  • 需要稳定排序

  • 处理复杂对象

  • 代码简洁性优先

  • 数组大小适中(< 1百万)


考虑其他方案当:

  • 需要绝对性能(TypedArray)

  • 内存极度受限(堆排序)

  • 特殊数据结构(如链表)

  • 只需要部分排序(选择算法)

相关推荐
伟大的车尔尼2 天前
双指针题目:两个数组的交集 II
排序·双指针·哈希表
Irene19912 天前
JavaScript 中常用排序方法的性能对比和分析
排序
howard20052 天前
Hive实战任务 - 9.3 实现学生信息排序和统计
hive·排序·汇总·学生信息
伟大的车尔尼5 天前
双指针题目:两个数组的交集
排序·双指针·哈希表
利刃大大6 天前
【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法
java·开发语言·枚举·lambda·排序
波波仔867 天前
clickhouse存储和分区
clickhouse·排序·分区
Tisfy7 天前
LeetCode 3606.优惠券校验器:分类 + 排序
leetcode·题解·排序
2401_8414956410 天前
【LeetCode刷题】合并区间
数据结构·python·算法·leetcode·合并·遍历·排序
.YM.Z14 天前
【数据结构】:排序(二)——归并与计数排序详解
数据结构·算法·排序