自定义ES6 reduce函数模拟map功能的原理及疑问解答
自定义ES6 Reduce函数实现解析及问题解答
首先咱们先把这段代码清晰展示出来:
const reduce = (reducer, accumulator) => arr => { const [first, ...rest] = arr return arr.length ? reduce(reducer, reducer(accumulator, first))(rest) : accumulator } reduce((accum, current) => [...accum, current * 2], [])([1, 2, 3]);
代码工作原理拆解
这个reduce是个柯里化函数(也就是分多次接收参数的函数),整体执行流程可以拆成两步:
- 第一次调用:传入
reducer(定义元素处理逻辑的函数)和accumulator(初始累积值),返回一个专门等待接收目标数组的新函数。 - 调用返回的新函数并传入要处理的数组后,内部会做这些操作:
- 用解构赋值把数组拆成第一个元素
first和剩余元素组成的rest数组。 - 判断数组长度:如果数组不为空,就递归调用
reduce——此时把更新后的累积值(调用reducer用当前累积值和first计算得到)传入,同时把rest作为下一次要处理的数组;如果数组为空,就返回最终的累积值。
- 用解构赋值把数组拆成第一个元素
拿示例中的调用来说:
- 初始调用
reduce((accum, current) => [...accum, current * 2], [])得到一个等待数组的函数,随后传入[1,2,3]。 - 第一次处理:
first=1,rest=[2,3],调用reducer([],1)得到[2],接着递归调用reduce(...)处理[2,3]。 - 第二次处理:
first=2,rest=[3],调用reducer([2],2)得到[2,4],递归处理[3]。 - 第三次处理:
first=3,rest=[],调用reducer([2,4],3)得到[2,4,6],递归处理空数组。 - 空数组触发返回逻辑,最终结果就是
[2,4,6]。
问题1:为何在reducer函数中...accum作为数组展开的第一个参数而非最后一个?该示例中...accum的具体含义是什么?
...accum的具体含义
...是ES6的扩展运算符,在这里...accum的作用是把accum数组里的所有元素逐个“拆解”出来,放到新数组的对应位置。比如accum是[2],...accum就相当于把2单独提取出来,直接作为新数组的一部分。
为什么放在第一个参数
这么做是为了保证处理后数组的顺序和原数组完全一致:
- 如果把
...accum放在前面:每次处理新元素时,先把之前累积的所有元素放到新数组里,再追加当前元素的处理结果(current*2),最终结果的顺序和原数组一一对应——原数组是[1,2,3],结果就是[2,4,6]。 - 如果把
...accum放在后面:新数组会先放当前元素的处理结果,再把之前的累积元素追加到后面,结果就会变成[6,4,2],完全倒序,这显然不符合我们模拟map函数的预期。
问题2:为何初始调用reduce时,第一个reducer并未传入任何参数?
其实你是混淆了柯里化函数的调用层级——初始调用reduce时是传入了reducer参数的!
看示例里的调用:reduce((accum, current) => [...accum, current * 2], [])([1, 2, 3]),这里第一个参数(accum, current) => [...accum, current * 2]就是我们要传入的reducer函数,第二个参数[]是初始累积值。
这个reduce函数采用了柯里化设计,分两次完成调用:
- 第一次调用
reduce(reducer函数, 初始累积值):完成前两个参数的传递,返回一个等待接收目标数组的函数。 - 第二次调用
(目标数组):才真正启动数组的遍历处理逻辑。
所以并不是没传reducer,只是它的调用形式是分两步的柯里化写法而已。
内容的提问来源于stack exchange,提问作者Mat.Now
相关产品推荐
相关产品推荐

