先记住这个答案
reduce 未提供 initialValue 时,需要找到第一个存在的元素作为累加器,再从后续索引调用回调。空数组没有这个起点,所以抛出 TypeError;全为空位且没有可读取属性的数组也会失败。提供初始值后,空输入直接返回该值且不会调用回调。初值应匹配运算的单位元或业务约定,例如求和可用零、收集结果可用新数组;最大值等问题则可能需要显式返回无结果或拒绝空输入,不能为了消除异常而引入错误默认值。
- 缺少初值时需要从输入里找到累加器
- 空输入有初值就直接返回初值
- 默认结果必须符合业务含义与结果类型
过滤链会把原本非空的数据变成空集
页面列表可能有很多记录,但过滤出符合条件的项目后一个也不剩。若后面直接 reduce 且不提供初值,就会在少见筛选条件下报错;原始接口返回非空不能证明聚合输入非空。
下面先过滤正数,再对结果求和。显式初值零表达没有正数时总和为零,同时让结果类型与正常路径一致;示例记录回调次数,说明这个零不是回调计算出来的。
const selected = [-2, 0].filter(value => value > 0);
try { selected.reduce((sum, value) => sum + value); }
catch (error) { console.log(error.name); }
let calls = 0;
const total = selected.reduce((sum, value) => { calls++; return sum + value; }, 0);
console.log(total, calls);
console.log([9].reduce((acc, value) => acc + value));查看输出与解释
TypeError
0 0
9无初值时失败,有初值时直接返回零且调用次数为零。单元素示例直接得到九,提醒校验逻辑不能只放在可能完全不执行的回调中。
不是每一种聚合都适合用零
求最大值时若所有数据为负数,初值零会得到一个输入中不存在的错误答案。可以要求至少一个有效值,或者用明确的无结果标记配合后续分支,数学无穷也需要考虑展示和序列化。
构造对象、数组或统计表时,初始累加器应是这次调用独有的结构,避免多次聚合共享可变初值而相互污染。类型系统里还应让累加器类型表达最终结构,不能用断言掩盖空结果设计缺失。
空位和输入校验要分开检查
一个 length 大于零但全为空位的普通数组,仍可能没有可用元素作为无初值 reduce 的起点。仅检查 length 不能证明回调会收到业务记录,应先确认数据结构和属性存在性。
如果业务不允许缺失或非法元素,应在聚合之前校验,或在一个明确遍历里统一处理。捕获所有 TypeError 再返回零会把回调里的真实程序错误也吞掉,不能作为默认修复方式。
容易答错的地方
- 给最大值聚合补零就认为问题解决
- 零会改变全负数输入的结果,消除异常并不等于保持语义。应分别测试空输入、单元素和全负数,再根据业务决定无结果、错误或合适的数学初始值。
- 认为非空 length 就保证 reduce 有起点
- 稀疏数组的长度包含不存在的索引,回调方法通常跳过这些位置。需要确认存在有效元素或显式提供初值,不能把容量式长度当作真实记录数量。
面试官还会怎么问?
显式传 undefined 算不算提供初值?
算,是否提供参数与参数值是不是 undefined 是不同问题。空数组会直接返回这个初值,而普通非空聚合的第一轮累加器也将是 undefined,因此调用者必须确保回调能处理该类型。
单个元素为何没有执行回调?
无初值时这个元素已经被取为累加器,没有后续元素需要折叠。若必须对每一项进行转换,应显式提供合适初值或先执行映射,不能依靠无初值 reduce 来保证回调调用。
reduceRight 的空输入规则一样吗?
缺少初始累加器时同样需要从输入里找到一个存在元素,只是查找和后续访问方向相反。空数组或全无可用元素仍会失败,空结果的业务约定不会因方向改变而自动产生。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。