JavaScript 教程实战:用 reduce 实现 getAverageAge 计算对象数组的平均年龄

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

JavaScript 教程实战:用 reduce 实现 getAverageAge 计算对象数组的平均年龄

导读

本篇以 Modern JavaScript Tutorial(en.javascript.info)仓库中的经典习题"Get average age"(任务描述)为线索,讲解如何对一个由 {name, age} 对象组成的数组使用 Array.prototype.reduce 求和并除以 length 得到平均年龄。你将掌握 reduce 累加器的正确用法、为空数组保留健壮性的写法,以及将"求平均"推广到"求总和/最大值/分组"等常见聚合场景的实战方法,并看到它在 数组方法章节 中的位置与原理支撑。


一、题目本身:从对象数组中求平均年龄

题目要求编写函数 getAverageAge(users),它接收一个带有 age 属性的对象数组,返回所有人的平均年龄。平均值的公式为:

(age1 + age2 + ... + ageN) / N

其中 N 是数组中对象的个数。给定示例数据:

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 29 };

let arr = [ john, pete, mary ];

alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28

这是 05-array-methods 章节末尾的一道综合练习(importance 4),考察的核心是"遍历数组 → 累加每个元素的某个属性 → 得到一个标量结果",而这正是 reduce 的典型使用场景。

二、官方参考解法:一行 reduce 完成求和

仓库中 solution.md 给出的参考解法非常精炼:

function getAverageAge(users) {
  return users.reduce((prev, user) => prev + user.age, 0) / users.length;
}

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 29 };

let arr = [ john, pete, mary ];

alert( getAverageAge(arr) ); // 28

拆解这一行代码:

  1. users.reduce((prev, user) => prev + user.age, 0):从初始值 0 开始,逐个对象累加 user.age,最终得到年龄总和 25 + 30 + 29 = 84;
  2. / users.length:除以人数 3,得到平均年龄 28。

这里 reduce 的回调只用到两个参数:prev(累加器,上一次调用的返回值)和 user(当前数组元素)。reduce 剩余两个可选参数 index 与 array 在此场景下并不需要。

reduce 的完整签名与执行过程

在 article.md 的 "reduce/reduceRight" 小节中,官方给出了完整语法:

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);
  • accumulator —— 上一次函数调用的结果,首次调用时等于 initial(若提供了初值);
  • item —— 当前数组元素;
  • index —— 当前元素位置;
  • array —— 数组本身。

以 [25, 30, 29](即依次取 user.age)配合初始值 0 为例,调用过程如下表:

调用次序 sum(累加器) current 返回结果
第 1 次 0 25 25
第 2 次 25 30 55
第 3 次 55 29 84

最终 84 成为 reduce 的返回值,再除以 3 即得平均年龄。

三、为什么初始值 0 必不可少

reduce 允许省略初始值:此时它会把数组第一个元素当作累加器的初值,并从第二个元素开始迭代。对求和来说结果通常一致,但存在一个致命边界:

let arr = [];

// Error: Reduce of empty array with no initial value
arr.reduce((sum, current) => sum + current);

当数组为空且未提供初始值时,reduce 会抛出 TypeError。因此 article.md 明确建议:始终显式指定初始值。getAverageAge 中的 , 0 正是这一最佳实践的体现——即使传入空数组,reduce 也会返回 0(只是再除以 0 会得到 NaN,若需完全健壮可加空数组判断,见下文扩展)。

四、让函数更健壮:处理空数组与缺失 age

生产环境中数据未必总是"规整",可以对官方解法做防御性增强:

function getAverageAge(users) {
  if (users.length === 0) return 0; // 避免 0/0 得到 NaN
  const total = users.reduce((sum, user) => sum + (user.age || 0), 0);
  return total / users.length;
}
  • 空数组提前返回 0,避免 0 / 0 = NaN;
  • user.age || 0 忽略 undefined/null 等缺失年龄字段,保证求和不会中途变成 NaN。

五、从"平均"到通用聚合:reduce 的进阶用法

reduce 的价值不止于求和。同章节的练习 Create keyed object from array 要求用 reduce 把用户数组按 id 聚合成对象,这正是 getAverageAge 所用技巧的延伸:

function groupById(arr) {
  return arr.reduce((obj, user) => {
    obj[user.id] = user;
    return obj;
  }, {});
}

同样的思路可以轻松迁移到其他聚合场景:

// 最大年龄
const maxAge = users.reduce((max, u) => Math.max(max, u.age), -Infinity);

// 年龄总和
const totalAge = users.reduce((sum, u) => sum + u.age, 0);

// 各年龄段人数统计
const byRange = users.reduce((acc, u) => {
  const key = u.age < 30 ? 'under30' : 'over30';
  acc[key] = (acc[key] || 0) + 1;
  return acc;
}, {});

可以看到,reduce 的第二个参数(初始值)决定了"聚合结果的形态":0 是数值累加,{} 是按键分组,[] 则可用于收集满足条件的元素。

六、小结

  • 求对象数组的平均年龄 = reduce 累加某属性 + 除以 users.length;
  • 始终为 reduce 提供初始值,避免空数组抛错;
  • 回调只需关心 accumulator 与 item 两个参数,即可完成求和、求最值、分组等绝大多数聚合任务;
  • 完整解法与逐步推导可对照 solution.md 与 数组方法章节 学习。
登录后查看全文
en.javascript.info