zh.javascript.info 教程实战:用 Array.sort 按年龄对对象数组排序(sortByAge 精讲)

原创2026-10-06 17:09:281,344 阅读
文章标签:文档教程

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

注意两个容易被忽略的细节:

  1. 输入数组故意是乱序的:pete(30)、john(25)、mary(28),只有排序函数正确,结果才会变成 john(25)、mary(28)、pete(30)。
  2. 排序后访问的是对象属性: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 优化),它会多次调用我们的比较函数,且每次比较只针对数组中的两个元素。因此编写比较函数时建议注意以下几点:

  1. 年龄字段缺失或非数值:a.age - b.age 遇到 undefined 会得到 NaN,而 NaN 参与比较时排序结果不可预期。生产代码建议先用 filter 或默认值处理缺失字段,再排序。
  2. 相等年龄保持稳定:现代 JS 引擎的 sort 是稳定排序(稳定排序是 ES2019 规范要求),相同 age 的对象会保持原有相对顺序,无需额外处理;但不要依赖比较函数返回 0 之外的值去“打乱”相等元素。
  3. 不要直接修改 age:排序移动的是数组元素(对象引用),对象属性本身不会被改写;若需要“按年龄复制一份排序结果”,先 slice() 再排序即可(对应 5-copy-sort-array 的思路)。
  4. 字符串字段排序用 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)逐题演练。

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