JavaScript Tutorial:用 sortByAge 对对象数组按 age 排序的完整实战解析

原创2026-10-02 15:37:191,041 阅读
文章标签:文档/教程前端

JavaScript Tutorial:用 sortByAge 对对象数组按 age 排序的完整实战解析

导读

本篇技术指南围绕《Modern JavaScript Tutorial》(en.javascript.info)中 数组方法章节 的经典习题「Sort users by age」展开,完整拆解如何编写 sortByAge(users) 函数,让包含 age 属性的对象数组按年龄升序排列。读完本文你将掌握 Array.prototype.sort 的比较函数机制(就地排序、字符串默认比较陷阱、返回值语义),并能举一反三实现降序、多字段、字符串字典序等真实场景的对象排序方案。

一、题目回顾:按年龄对用户对象排序

任务位于 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, john, mary](30、25、28),本身就是乱序,用于检验排序效果;
  • 期望结果 [john, mary, pete](25、28、30)按年龄升序排列。

二、官方解法:一行比较函数搞定

仓库中的官方解答位于 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);

// now sorted is: [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)。它把「比较对象」这件事完全交给比较函数:每个元素是对象,取各自 age 属性做数值相减。

三、原理拆解:sort(fn) 的比较函数契约

要理解这行代码为什么成立,需要回到 数组方法章节 对 arr.sort(fn) 的讲解。三个要点缺一不可:

1. sort 是就地排序

调用 arr.sort() 会直接修改原数组的元素顺序,同时返回排序后的数组(返回值通常被忽略,因为 arr 本身已被改写)。因此 sortByAge(arr) 无需返回值——调用方持有的 arr 引用在函数返回后就已经是有序的了,这正是题目中 sortByAge(arr) 之后立即 alert(arr[0].name) 能拿到正确结果的原因。

2. 默认比较是字符串字典序(大坑)

不带参数调用 arr.sort() 时,所有元素会先被转成字符串再按字典序比较:

let arr = [ 1, 2, 15 ];
arr.sort();
alert( arr );  // 1, 15, 2 —— 数字被当作字符串比较,"2" > "15"

对对象数组直接 arr.sort() 更糟:每个对象转成字符串都是 "[object Object]",比较结果完全无意义。这正是本习题必须显式传入比较函数的原因。

3. 比较函数的返回值语义

sort(fn) 会反复调用 fn(a, b) 决定两个元素的相对顺序,约定如下:

function compare(a, b) {
  if (a > b) return 1;   // a 排在 b 后面
  if (a == b) return 0;  // 保持相对位置
  if (a < b) return -1;  // a 排在 b 前面
}

而 章节内 smart 提示 进一步放宽了要求:比较函数只需返回正数表示「大于」、负数表示「小于」即可,不必拘泥于 ±1。于是减法 a - b 成为最简洁的写法:

  • a.age > b.age → 结果为正 → a 靠后;
  • a.age < b.age → 结果为负 → a 靠前;
  • a.age === b.age → 结果为 0 → 两者相对顺序不变。

a.age - b.age 恰好完整编码了这三个分支,这就是官方解法一行解决的全部原理。

四、实战扩展:从单字段升序到多场景排序

掌握了比较函数契约后,同一模式可以覆盖绝大多数对象排序需求。

1. 按 age 降序

arr.sort((a, b) => b.age - a.age);

对照仓库中同章节习题 4-sort-back 的 arr.sort((a, b) => b - a),对象场景只需把 a、b 换成 a.age、b.age。

2. 多字段排序(age 相同再按 name)

arr.sort((a, b) => {
  if (a.age !== b.age) return a.age - b.age;  // 主排序:年龄
  return a.name.localeCompare(b.name);        // 次排序:姓名字典序
});

3. 字符串字段排序用 localeCompare

当排序字段是字符串(如 name),直接 a.name - b.name 会得到 NaN(无意义)。章节的 localeCompare 提示 指出:按字母码比较对 Ö 等带重音字母会出错,应使用 str.localeCompare:

let countries = ['Österreich', 'Andorra', 'Vietnam'];

alert( countries.sort( (a, b) => a.localeCompare(b) ) );
// Andorra, Österreich, Vietnam —— 正确!

4. 保持原数组不变:先拷贝再排序

sort 就地修改数组,若业务上需要保留原数据,先拷贝再排:

function sortByAgeCopy(arr) {
  return arr.slice().sort((a, b) => a.age - b.age);
}

同章节习题 5-copy-sort-array 正是这一思路的练习;slice() 无参调用即返回浅拷贝(见 article.md 的 slice 小节)。

5. 比较函数必须是纯函数

章节姊妹习题 9-shuffle 给出了反例警示:若在比较函数里塞随机数(Math.random() - 0.5),排序算法(多数引擎为 Timsort/快速排序的优化实现)会把「黑箱」行为放大,导致各排列概率严重不均。因此比较函数应保持确定性(同样的输入永远给出同样的结论),只依据元素自身的可比较属性返回结果。

五、验证与进一步练习

官方解答标注了 run 标记,可直接在浏览器环境执行验证三段 alert 依次输出 John、Mary、Pete。你也可以自行构造边界用例来加深理解:

  • 年龄相同的对象:验证相对顺序是否保持(现代 JS 引擎的 sort 是稳定排序);
  • 空数组 / 单元素数组:sort 可直接处理,无需特判;
  • age 为字符串数字(如 "25"):减法会触发隐式数值转换,仍能正确排序,但建议数据源统一类型。

本习题属于 数组方法章节 对象操作训练链的一环,同组习题还包括按字段名排序的 7-map-objects、按属性取值的 6-array-get-names,通读这些任务可系统掌握「数组方法 + 对象属性」的组合拳。

小结

sortByAge 的官方解法把本习题的全部考点浓缩进一行 arr.sort((a, b) => a.age - b.age):就地排序、显式比较函数、正负零返回值语义、属性取值与减法速记。理解这条契约后,任何「对象数组按某字段排序」的需求——升序、降序、多条件、字符串字典序——都能用同一套模式稳定落地。

登录后查看全文
en.javascript.info