日常开发里,列表、表格、统计几乎都绕不开「对象数组」的排序和分组。本文不讲底层原理,只讲怎么选、为什么选、容易踩哪些坑。适合会写 JS 但概念有点混的同学,也适合想补齐基础的前端老手。
一、Array.sort 到底在干什么
1.1 三个关键点
| 要点 | 说明 |
|---|---|
| 原地排序 | sort() 会直接修改原数组,不会返回新数组 |
| 默认行为 | 不传比较函数时,按字符串逐个字符比较 |
compare 返回值 |
负数:a 排前面;0:不变;正数:b 排前面 |
有没有同学会有这样的疑问:compare 返回值?这是啥? 解释:
- 这里的
compare指的是Array.sort()方法中传入的比较函数(也就是你后面写的(a, b) => a - b这种形式)。 - 简单说:当你用
sort()排序时,传入的这个函数就是compare,它的作用是告诉sort()两个元素(a和b)该怎么排,返回值直接决定排序结果,和表格里的说明完全对应。 - 比如
nums.sort((a, b) => a - b)中,(a, b) => a - b就是compare比较函数。
1.2 第一个坑:数字数组直接用 sort
javascript
const nums = [10, 2, 1];
nums.sort(); // 这一步已经把原数组 nums 改了!以为会得到 [1, 2, 10]
console.log(nums); // 打印的是被修改后的原数组,不是初始值。实际得到 [1, 10, 2] ------ 按字符串 "10"、"2"、"1" 比较了!
// ✅ 正确写法
nums.sort((a, b) => a - b); // 升序 [1, 2, 10]
nums.sort((a, b) => b - a); // 降序 [10, 2, 1]
问 :为什么按字符串比较会得到 [1, 10, 2]? 答 :sort() 默认的字符串比较规则是「逐字符按 Unicode 码点比较」,不是看数字大小,步骤拆解如下:
- 先把数组里的数字都转成字符串:10→"10"、2→"2"、1→"1";
- 从第一个字符开始比,字符的
Unicode码点:"1"(码点 49)< "2"(码点 50); - 具体比较过程:
- 比较 "1" 和 "10":第一个字符都是 "1"(码点相同),但 "1" 没有第二个字符,所以 "1" < "10";
- 比较 "10" 和 "2":第一个字符 "1" < "2",所以 "10" < "2"
1.3 第二个坑:原数组被改了
javascript
const original = [3, 1, 2];
const sorted = original.sort((a, b) => a - b);
console.log(sorted); // [1, 2, 3]
console.log(original); // [1, 2, 3] ------ 原数组也被改了!
// ✅ 需要保留原数组时,先浅拷贝再排序
const sorted2 = [...original].sort((a, b) => a - b);
二、对象数组按不同字段排序
2.1 按数字排序
javascript
const users = [
{ name: '张三', age: 25 },
{ name: '李四', age: 18 },
{ name: '王五', age: 30 }
];
// 按 age 升序
users.sort((a, b) => a.age - b.age);
// 结果:李四(18) → 张三(25) → 王五(30)
// 按 age 降序
users.sort((a, b) => b.age - a.age);
写法记忆 :升序 a - b,降序 b - a。
2.2 按字符串排序
javascript
// 按 name 字母/拼音顺序
users.sort((a, b) => a.name.localeCompare(b.name));
直接用 a.name > b.name ? 1 : -1 可以工作,但遇到中文、大小写、多语言时容易出问题,所以更推荐 localeCompare,后面会细讲。
2.3 按日期排序
日期有两种常见形式:字符串和时间戳。
javascript
const orders = [
{ id: 1, date: '2025-02-15' },
{ id: 2, date: '2025-01-20' },
{ id: 3, date: '2025-02-10' }
];
// 方式一:YYYY-MM-DD 格式的字符串可以直接用 localeCompare
orders.sort((a, b) => a.date.localeCompare(b.date));
// 方式二:转时间戳(适用各种日期格式)
orders.sort((a, b) => new Date(a.date) - new Date(b.date));
建议 :后端返回的日期如果是 YYYY-MM-DD,用 localeCompare 即可;格式不统一时,统一用 new Date() 转时间戳再比较。
2.4 多字段排序
先按 A 排序,A 相同再按 B 排序,可以用 || 链式比较:
javascript
users.sort((a, b) => {
if (a.age !== b.age) return a.age - b.age; // 先按年龄
return a.name.localeCompare(b.name); // 年龄相同再按姓名
});
// 更简洁的写法
users.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name));
原理 :a.age - b.age 为 0 时,0 || xxx 会取后面的 localeCompare 结果。
三、localeCompare:字符串排序的正确姿势
3.1 为什么不用 >、< 比较字符串?
javascript
const arr = ['张三', '李四', '王五', 'apple', 'Apple'];
arr.sort((a, b) => a > b ? 1 : -1); // 按 Unicode 比较,中文结果不符合直觉
arr.sort((a, b) => a.localeCompare(b)); // 按语言规则,更符合人类习惯
localeCompare 可以:
- 中文按拼音
- 控制大小写敏感
- 数字按数值比较(如 "10" 在 "2" 后面)
3.2 常用用法
javascript
// 指定语言(中文按拼音)
'张三'.localeCompare('李四', 'zh-CN'); // 负数,张在李后面
// 忽略大小写
'apple'.localeCompare('Apple', undefined, { sensitivity: 'base' }); // 0,视为相等
// 数字按数值比较
['10', '2', '1'].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
// 结果:['1', '2', '10']
3.3 兼容性说明
现代浏览器和 Node 都支持 localeCompare。带 options 配置的 localeCompare 写法,在老环境(旧浏览器 / 旧 Node 版本)中可能表现不一致,生产环境建议先小范围验证。
javascript
// 忽略大小写(options:{ sensitivity: 'base' })
'apple'.localeCompare('Apple', undefined, { sensitivity: 'base' });
// 数字按数值比较(options:{ numeric: true })
['10','2'].sort((a,b) => a.localeCompare(b, undefined, { numeric: true }));
老环境问题:像旧版 IE、低版本 Node(比如 Node.js 10 以下),对这些options配置支持不完善(比如不识别numeric: true),导致排序结果出错,所以生产环境要先小范围验证。
3.4 补充localeCompare带options写法 老环境兼容技巧
核心兼容思路:降级处理------先判断环境是否支持localeCompare的options配置,支持则用带options的简洁写法,不支持则降级为基础写法,保证排序效果一致,且代码简单可直接套用(无需额外引入兼容库)。
场景1:忽略大小写排序(对应options: { sensitivity: 'base' })音标:/sensəˈtɪvəti/
老环境兼容写法(适配旧IE、低版本Node):
javascript
// 兼容函数:忽略大小写比较两个字符串
function compareIgnoreCase(a, b) {
// 先统一转小写,再用基础localeCompare(老环境均支持无options写法)
const lowerA = a.toLowerCase();
const lowerB = b.toLowerCase();
return lowerA.localeCompare(lowerB, 'zh-CN'); // 中文场景可加语言标识
}
// 用法(和带options写法效果一致)
const arr = ['apple', 'Apple', 'Banana', 'banana'];
arr.sort(compareIgnoreCase); // 结果:['apple', 'Apple', 'Banana', 'banana']
场景2:数字字符串按数值排序(对应options: { numeric: true })
老环境兼容写法(避免老环境不识别numeric 音标:/njuːˈmerɪk/ 配置导致排序错乱):
javascript
// 兼容函数:数字字符串按数值排序
function compareNumericStr(a, b) {
// 降级思路:转成数字比较(贴合原文数字排序逻辑,老环境完全支持)
const numA = Number(a);
const numB = Number(b);
return numA - numB; // 升序,降序则改为numB - numA
}
// 用法(和带options写法效果一致)
const arr = ['10', '2', '1', '25'];
arr.sort(compareNumericStr); // 结果:['1', '2', '10', '25']
关键注意点
-
无需判断环境:上述兼容写法兼容所有环境(老环境正常运行,新环境也不影响效果),不用额外写环境判断代码,简化开发。
-
生产环境验证:如果老环境占比极低,可直接用带
options写法,上线前用老环境(如IE11、Node.js 8)简单测试1个排序案例即可。
四、分组统计:从排序到 groupBy 【分组】
排序和分组是两个不同操作:
- 排序:改变顺序,不拆分数组
- 分组:按某个字段把数组拆成多组
JS 没有内置 groupBy,可以用 reduce 实现:
javascript
const orders = [
{ id: 1, status: 'paid', amount: 100 },
{ id: 2, status: 'pending', amount: 50 },
{ id: 3, status: 'paid', amount: 200 }
];
const byStatus = orders.reduce((acc, item) => {
const key = item.status;
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
// 结果:
// {
// paid: [{ id: 1, ... }, { id: 3, ... }],
// pending: [{ id: 2, ... }]
// }
分组后再排序
分组后,如果每组内部还要排序:
javascript
Object.keys(byStatus).forEach(key => {
byStatus[key].sort((a, b) => b.amount - a.amount); // 每组按金额降序
});
分组 + 统计
需要同时统计每组数量或汇总值时:
javascript
const stats = orders.reduce((acc, item) => {
const key = item.status;
if (!acc[key]) {
acc[key] = { list: [], total: 0, count: 0 };
}
acc[key].list.push(item);
acc[key].total += item.amount;
acc[key].count += 1;
return acc;
}, {});
// 结果示例:{ paid: { list: [...], total: 300, count: 2 }, ... }
五、踩坑速查表
| 坑点 | 错误表现 | 正确写法 |
|---|---|---|
| 数字数组排序错乱 | [10, 2, 1].sort() → [1, 10, 2] |
arr.sort((a, b) => a - b) |
| 原数组被修改 | 排序后原数组也变了 | [...arr].sort(...) |
| 中文排序不对 | 直接用 >、< 比较 |
a.localeCompare(b, 'zh-CN') |
| 多字段排序只写了一层 | 只按第一个字段排 | `a.age - b.age |
| 日期格式不统一 | 字符串比较出错 | new Date(a.date) - new Date(b.date) |
六、小结
- 数字排序 :用
(a, b) => a - b或b - a,不要用默认sort()。 - 字符串排序 :优先用
localeCompare,尤其是中文和多语言场景。 - 日期排序 :
YYYY-MM-DD用localeCompare,其他格式用时间戳。 - 多字段排序 :用
||串联多个比较。 - 分组 :用
reduce做groupBy,再按需对每组排序或统计。 - 保留原数组 :排序前先
[...arr]浅拷贝。
这些写法足够覆盖大部分日常需求,记住上面的速查表,可以少踩很多坑。
以上就是本次的学习分享,欢迎大家在评论区讨论指正,与大家共勉。
我是 Eugene,你的电子学友。
如果文章对你有帮助,别忘了点赞、收藏、加关注,你的认可是我持续输出的最大动力~