zh.javascript.info 教程实战:用 Array.sort 按年龄对对象数组排序(sortByAge 精讲)
zh.javascript.info 教程实战:用 Array.sort 按年龄对对象数组排序(sortByAge 精讲)
本篇技术指南以《现代 JavaScript 教程》(zh.javascript.info)数组方法章节中的经典练习 “按年龄对用户排序”(8-sort-objects) 为核心,完整讲解如何为对象数组编写比较函数,并通过 sortByAge 一行代码解决按 age 属性升序排序的问题。读完本文,你将掌握 Array.prototype.sort 的原位排序机制、比较函数返回值约定,以及对象数组在实际业务场景(用户列表、排行榜、表格数据)中的排序套路,并能顺手解决同章节的降序排序、复制排序等变体问题。
任务速览:sortByAge 要解决什么问题
任务位于 1-js/05-data-types/05-array-methods/8-sort-objects/task.md,原始要求非常简洁:编写函数 sortByAge(users),读取对象数组的 age 属性,并根据 age 对数组中的对象进行排序。题目给出的数据如下:
let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 28 };
let arr = [ pete, john, mary ];
sortByAge(arr);
// now: [john, mary, pete]
alert(arr[0].name); // John
alert(arr[1].name); // Mary
alert(arr[2].name); // Pete
注意两个容易被忽略的细节:
- 输入数组故意是乱序的:
pete(30)、john(25)、mary(28),只有排序函数正确,结果才会变成john(25)、mary(28)、pete(30)。 - 排序后访问的是对象属性:
arr[0].name必须输出John,这验证了排序移动的是数组中的对象引用,对象本身(及其name、age属性)保持不变。
参考解法:一行箭头函数完成升序排序
官方参考解法见 1-js/05-data-types/05-array-methods/8-sort-objects/solution.md:
function sortByAge(arr) {
arr.sort((a, b) => a.age - b.age);
}
let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 28 };
let arr = [ pete, john, mary ];
sortByAge(arr);
// 排序后的数组为:[john, mary, pete]
alert(arr[0].name); // John
alert(arr[1].name); // Mary
alert(arr[2].name); // Pete
解法只有一条核心语句:arr.sort((a, b) => a.age - b.age)。它之所以成立,依赖的是本章 array-methods/article.md 中 sort(fn) 一节讲解的三条原理。理解它们,才算真正吃透这道题。
原理一:sort 是原位(in-place)排序
arr.sort(fn) 对数组进行原位排序,直接更改原数组中元素的顺序,并返回排序后的数组。返回值通常被忽略,因为 arr 本身已经被修改了。这正是题目要求 sortByAge(arr) 直接修改传入数组、之后用 alert(arr[0].name) 验证结果的前提——函数内部不需要 return 新数组,只需对入参数组调用 sort 即可。
对比:默认 sort 的“字符串排序”陷阱
教程 article.md 里特意用一个例子提醒初学者:直接调用 arr.sort() 会把所有元素转换为字符串再按字典序比较:
let arr = [ 1, 2, 15 ];
arr.sort();
alert( arr ); // 1, 15, 2
结果变成 1, 15, 2,因为字符串 "15" 按字典序排在 "2" 前面。同理,如果本题直接调用 arr.sort(),对象会被转成 "[object Object]" 这样的字符串,排序毫无意义。所以对对象数组排序,必须提供自定义比较函数。
原理二:比较函数的返回值约定
教程给出了比较函数的标准形态:
function compare(a, b) {
if (a > b) return 1; // 第一个值比第二个值大
if (a == b) return 0; // 两个值相等
if (a < b) return -1; // 第一个值比第二个值小
}
关键约定(教程在“比较函数可以返回任何数字”提示框中明确说明):比较函数只需要返回正数表示“大于”、负数表示“小于”,并不要求必须是 1 或 -1。于是针对数值属性,可以用减法把三分支压缩成一行:
arr.sort(function(a, b) { return a - b; }); // 数值升序
a.age - b.age 的语义拆解:
a.age < b.age时结果为负数 →a排在b前面(升序);- 相等时为
0→ 顺序不变(排序算法视实现而定,现代引擎多为稳定排序); a.age > b.age时为正数 →a排在b后面。
这就是 sortByAge 解法的全部数学内核。
原理三:箭头函数让比较函数更简洁
教程紧接着给出箭头函数版本(见“箭头函数最好”提示框):
arr.sort( (a, b) => a - b );
它与上面更长的 function 版本完全等价,区别只是语法更短。本题解法的 (a, b) => a.age - b.age 正是这种写法的对象版:箭头函数的两个参数 a、b 分别是数组中的两个相邻待比较对象,函数体直接读取它们的 age 字段做减法。
举一反三:同章节的三个变体练习
理解了上述原理,同章节的其他排序/对象数组练习题都能顺手解决,这也证明 sort 比较函数是这一章的核心技能点。
1. 降序排列(4-sort-back)
4-sort-back/task.md 要求把 <a href="https://link.gitcode.com/i/b83ab1b3cc8ecdc1a33fed9bfd3fbf4c" target="_blank">5, 2, 1, -10, 8] 降序排列,官方解法见 [solution.md:
let arr = [5, 2, 1, -10, 8];
arr.sort((a, b) => b - a);
alert( arr ); // 8, 5, 2, 1, -10
与升序版对比:b - a 把减法的两个操作数对调,符号反转,排序方向随之反转。想给对象数组按 age 降序,同样只需 arr.sort((a, b) => b.age - a.age)。
2. 复制后排序(5-copy-sort-array)
5-copy-sort-array/task.md 要求得到排序副本但保持原数组不变。因为 sort 是原位修改,必须先复制再排序:
let sorted = arr.slice().sort();
slice() 无参调用会返回原数组的浅拷贝副本(见 article.md 中 slice 一节),副本上排序不影响原数组。这与 sortByAge 直接修改入参的语义正好互补:前者是“复制 + 排序”,后者是“原位排序”。
3. 对象数组的其他处理(7-map-objects 与 10-average-age)
同章还有两道配套练习可以巩固对象数组操作:
- 7-map-objects:用
map把{ name, surname, id }数组映射成{ fullName, id }数组——排序前的数据整形常用map; - 10-average-age:用
reduce求对象数组age的平均值——排序后的排名、统计场景常与reduce配合。
边界情况与工程实践建议
从源码级视角看,arr.sort(fn) 内部是通用排序算法(教程指出现代引擎通常基于快排或 Timsort 优化),它会多次调用我们的比较函数,且每次比较只针对数组中的两个元素。因此编写比较函数时建议注意以下几点:
- 年龄字段缺失或非数值:
a.age - b.age遇到undefined会得到NaN,而NaN参与比较时排序结果不可预期。生产代码建议先用filter或默认值处理缺失字段,再排序。 - 相等年龄保持稳定:现代 JS 引擎的
sort是稳定排序(稳定排序是 ES2019 规范要求),相同age的对象会保持原有相对顺序,无需额外处理;但不要依赖比较函数返回0之外的值去“打乱”相等元素。 - 不要直接修改
age:排序移动的是数组元素(对象引用),对象属性本身不会被改写;若需要“按年龄复制一份排序结果”,先slice()再排序即可(对应 5-copy-sort-array 的思路)。 - 字符串字段排序用
localeCompare:如果将来要按name排序,直接a.name > b.name ? 1 : -1对带变音符号的字符会出错,教程建议使用a.name.localeCompare(b.name)(见 article.md 的 “localeCompare” 提示框)。
小结
“按年龄对用户排序”这道题是数组方法章节中 sort(fn) 知识点的最佳实战载体,一行 arr.sort((a, b) => a.age - b.age) 背后浓缩了三个核心原理:
sort是原位排序,直接修改原数组;- 比较函数按正数 / 负数 / 零的约定决定先后顺序,减法写法是数值比较的最简形式;
- 箭头函数让比较函数保持简洁可读。
掌握它之后,升序(a - b)、降序(b - a)、复制后排序(slice().sort())以及对象数组的各种属性排序都能信手拈来。想继续巩固,可直接查看 数组方法章节完整文章 及其余练习任务目录(如 8-sort-objects、4-sort-back、5-copy-sort-array)逐题演练。