- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
导读
本文围绕 Modern JavaScript Tutorial(javascript.info)array-methods 章节 中的经典练习题「Get average age」展开:编写getAverageAge(users)函数,对包含age属性的对象数组求平均年龄。通过这道题,你将掌握Array.prototype.reduce的完整用法——包括累加器语义、初始值参数、空数组陷阱,以及它与for..of、map等方法的取舍,最终能独立处理“对象数组聚合计算”这一类常见业务场景。
任务目标:对对象数组求平均年龄
练习任务定义在 10-average-age/task.md,要求如下:
- 输入:一个由对象组成的数组,每个对象至少带有
age数值属性; - 输出:所有
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注意:这里的平均值就是普通算术平均数,不涉及加权、取整或去尾。示例结果为28,恰好是整数,但实际计算中25 + 30 + 29 = 84,84 / 3 = 28,分母是数组长度而非对象个数之外的任何额外因素。
参考答案:一行reduce搞定聚合
仓库中 10-average-age/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这段代码的逻辑分两步:
users.reduce((prev, user) => prev + user.age, 0)—— 把整个数组“折叠”成一个总和:0 + 25 + 30 + 29 = 84;- 将总和除以
users.length(即3),得到平均值28。
solution.md中的代码块带有js run标记,这是 javascript.info 教程源码特有的约定,表示该代码段可以被交互式运行验证——仓库中所有带此标记的练习解法都可以直接在浏览器控制台或任何支持 ES5+ 的环境执行。
深入原理:reduce的累加器机制
要真正理解上面的解法,需要先吃透Array.prototype.reduce。它在 array-methods/article.md 的「reduce/reduceRight」小节中有系统讲解,语法为:
let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]);回调函数的四个参数:
| 参数 | 含义 |
|---|---|
accumulator | 上一次调用的返回结果;首次调用时等于initial(若提供) |
item | 当前数组元素 |
index | 当前元素的索引 |
array | 数组本身 |
reduce的核心思想是“携带结果前行”:每一次回调的返回值,都会作为下一次回调的第一个参数accumulator传入。全部元素遍历完后,最后一次回调的返回值就是整个reduce的结果。
在本题中,回调只用了两个参数(prev与user),这是最常见的用法:
- 第一次调用:
prev = 0(初始值),user = john,返回0 + 25 = 25; - 第二次调用:
prev = 25,user = pete,返回25 + 30 = 55; - 第三次调用:
prev = 55,user = mary,返回55 + 29 = 84。
最终reduce返回84。下面是同样的过程用表格呈现(各行的prev即上一行的结果):
| 调用次数 | prev(累加器) | user(当前项) | 返回结果 |
|---|---|---|---|
| 第 1 次 | 0 | {name:"John", age:25} | 25 |
| 第 2 次 | 25 | {name:"Pete", age:30} | 55 |
| 第 3 次 | 55 | {name:"Mary", age:29} | 84 |
上图(仓库中的 reduce.svg)展示了reduce在数组元素间传递累加结果的数据流,正是本文求和的执行过程。
为什么必须显式传入初始值0
solution.md中特意写了, 0这个第二参数,这并非可有可无。reduce允许省略初始值:
let result = arr.reduce((sum, current) => sum + current); // 同样返回 15省略时,reduce会把数组第一个元素当作初始accumulator,并从第二个元素开始迭代。但原文档明确指出这种做法有隐患:如果数组为空,且没有提供初始值,reduce会直接抛出错误(TypeError: Reduce of empty array with no initial value)。
let arr = []; arr.reduce((sum, current) => sum + current); // TypeError!而对于本题这种“可能传入空数组”的通用工具函数来说,显式指定0还能保证:空数组时reduce返回0,随后0 / 0 = NaN,至少不会抛异常。因此:
- 在求和的聚合场景,永远显式传入初始值
0; - 若希望空数组返回
0而非NaN,可先判断长度,见下文“健壮性改造”。
reduceRight:从右向左的同门兄弟
reduce还有反向版本Array.prototype.reduceRight,回调参数含义完全相同,只是遍历方向从右到左。对于“求和”这类满足交换律的运算,两者结果一致;但在处理顺序敏感的累积(如从右往左拼接字符串、解析后缀表达式)时,reduceRight才有用武之地。
从求和到平均:一次reduce演算两个经典练习
reduce在 javascript.info 教程的练习体系中反复出现,是理解“对象集合聚合”的一把钥匙。除了本题,仓库中还有两个同族练习可以对照学习:
- 12-reduce-object(12-reduce-object/_js.view/solution.js):用
reduce把对象数组[{name:"John",salary:100}, ...]折叠成按name索引的映射对象,核心写法同样是array.reduce((obj, value) => { obj[value.name] = value.salary; return obj; }, {})——每次迭代都要显式返回累加器,这是对象聚合与数值求和的共同关键点; - Sum the properties(01-sum-salaries/task.md 与更早的 5-sum-object/task.md):对对象而非数组求和,需要先用
Object.values取出数值序列再累加。
三者放在一起可以看出:聚合计算的本质是先“取数”(user.age或Object.values),再“折叠”(reduce)。平均年龄 = 年龄总和 ÷ 人数,正是这套思路的典型应用。
健壮性改造与常见边界情况
参考答案追求极简,作为生产级函数还可以补齐边界处理:
1. 空数组与除零
function getAverageAge(users) { if (users.length === 0) return 0; // 约定:空数组平均值为 0 return users.reduce((prev, user) => prev + user.age, 0) / users.length; }0 / 0的结果是NaN,多数业务场景希望空数据返回中性值(0或null),因此前置守卫是常见做法。
2. 非整数结果与展示格式化
年龄均值常为小数(如三人的年龄是 25、30、31,均值为28.666...)。按需求保留位数:
// 保留一位小数 function getAverageAge(users) { const sum = users.reduce((prev, user) => prev + user.age, 0); return users.length ? Math.round((sum / users.length) * 10) / 10 : 0; }3. 数据缺失防护(可选)
如果数组中可能存在没有age属性的对象,user.age会是undefined,参与加法后产生NaN。可在回调内兜底:(prev, user) => prev + (user.age || 0),但这属于对脏数据的宽容处理,需要依据业务确定语义。
替代实现与选型对比
reduce不是唯一答案,理解其它写法有助于在不同场景下选择:
// for..of 循环:可读性最高,适合需要中途 break 的场景 function getAverageAge(users) { let sum = 0; for (const user of users) sum += user.age; return users.length ? sum / users.length : 0; } // map + reduce:把“取数”与“折叠”显式分离 function getAverageAge(users) { const ages = users.map(user => user.age); // 先取出所有年龄 return ages.reduce((prev, age) => prev + age, 0) / users.length; } // forEach:需要额外维护外部变量 function getAverageAge(users) { let sum = 0; users.forEach(user => { sum += user.age; }); return sum / users.length; }选型建议:
- 数据量小、追求一行式表达:
reduce最合适,仓库标准答案即此方案; - 需要提前退出(如检测到异常数据就停止):
for..of配合break更自然,reduce无法中途中断; - 强调关注点分离、便于阅读:
map+reduce把“抽取字段”和“聚合数值”拆成两步,语义更直白。
结语
「Get average age」这道题虽短,却完整覆盖了reduce的核心知识面:累加器参数、初始值语义、空数组陷阱、除零防护,以及与for..of、map的取舍。在 javascript.info 的练习体系中,它与 12-reduce-object、sum-salaries 共同构成了“数组/对象聚合计算”的完整训练闭环。掌握这一模式后,求和、平均值、最大值、分组、计数等日常数据处理任务都可以用同样简洁的reduce表达式完成。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
JavaScript 对象方法与 this 关键字详解(Modern JavaScript Tutorial 实战篇)
JavaScript 对象方法与 this 关键字详解(Modern JavaScript Tutorial 实战篇) 导读 在 JavaScript 中,对象
文档/教程前端JavaScript 计算器对象实战:用 Object Methods 与 this 实现可交互的 calculator(Modern JavaScript Tutorial)
JavaScript 计算器对象实战:用 Object Methods 与 this 实现可交互的 calculator(Modern JavaScript T
文档/教程前端json-render 中 Remotion Composition 的定义与动态元数据:从 Root.tsx 到 Timeline Spec
json render 中 Remotion Composition 的定义与动态元数据:从 Root.tsx 到 Timeline Spec 本文围绕 ski
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考