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
)
};
}
十、总结
最佳实践:
-
始终提供比较函数(除非明确需要字符串排序)
-
注意原地修改 - 必要时先复制数组
-
复杂比较预计算 - 提高性能
-
利用稳定性 - 多条件排序依赖稳定排序特性
-
考虑数据特性 - 部分有序数据 Timsort 表现更好
选择 sort() 当:
-
需要稳定排序
-
处理复杂对象
-
代码简洁性优先
-
数组大小适中(< 1百万)
考虑其他方案当:
-
需要绝对性能(TypedArray)
-
内存极度受限(堆排序)
-
特殊数据结构(如链表)
-
只需要部分排序(选择算法)