TypeScript/JavaScript函数式编程
翻译自: 《How to understand reduce()?》 - Andrew Zheng
reduce
是另一个函数式编程(Function Programming)概念,它在JavaScript中是作为一个Array方法:Array.prototype.reduce()
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。[1]
上面关于该方法的MDN说明太简单了,有时会引起人们的困惑。实际上,reduce
确实非常强大,我认为有两个用例最契合使用它:折叠列表(list)和转换初始值(initial value)
1. reduce参数
首先,让我们看一下reduce
中涉及的3个参数:
- list
- reducer(一个reduce函数)
- initial value(有时可以忽略,如果不存在,默认为列表中的第一项)
例如:
function sum(sum: number, item: number): number {
return sum + item;
}
const list = [1,2,3];
const result1 = Array.prototype.reduce.call(list, sum); // -> 6
const result2 = Array.prototype.reduce.call(list, sum, 1); // -> 7
list是一个数组,reducer是sum
函数以及initial value在第二种情况下初始值为1.
2. 输入/输出形状
Shape of Input/Output
为了更好地理解reduce
,让我们来定义一个“ 输入 / 输出 ”形状。我们都知道一个函数接受(多个)输入并生成输出。在纯函数中是没有副作用的,因此可以说,当我们调用纯函数时,它只是一个将输入转换输出的过程。
例如,以下函数输入2个数字并输出1个数字。这看起来像:[], number -> []
function sum(a: number[], b: number): number[] {
return [...a, b]
}
你可以将一块岩石堆放在一堆岩石上来进行可视化处理:
现在,考虑到该形状概念,我们可以将reduce
的用法分为两种情况:
- 折叠列表(list),这看起来像:
Object[] -> Object
- 转换初始值(initial value),这看起来像:
Object -> Object
2.1. 折叠列表(list):Object[] -> Object
这是为了从列表中转换输入并输出单个值。
reduce
有时也被成为fold
(折叠),我们可以通过下面图片中长毯子折叠成垫子上获得提示:
reduce
的大多数示例都属于此类。
案例1:计算数组之和
function sum(sum: number, item: number): number {
return sum + item;
}
const list = [1,2,3];
const result = Array.prototype.reduce.call(list, sum); // -> 6;
实现过程:
- 将列表(list)中的第一项作为初始值(initial value)
- 遍历列表(list)
- 逐一调用reducer函数,并将结果链接到下一个迭代
案例2:数组中找到最大值
基于输入和输出形状,它将一个列表转换为一个值,所以我们还可以这样使用reduce
:
function max(a: number, b: number): number {
return a > b ? a : b;
}
const list = [1,5,3];
const result = Array.prototype.reduce.call(list, max); // -> 5
2.2. 转换初始值(initial value):Object -> Object
除了变换列表(list),我们还可以变换初始值(initial value)。这种情况就像传入然后递归调用reducer函数逐一转换初始值(initial value)并链接输出。
想象一下,你和你的粉丝击掌,你是初始值,粉丝们是列表,击掌动作是reducer函数。
案例3:用替换选项列表替换uri参数
假如你有一个uri https://abc.com/:id/docs/:docId/history并且你想将:id
替换成用户id。
考虑一下上面提到的形状函数是将对象(uri)转换为另一个uri(替换参数),因此我们可以这样做:
const replacementOptions = [
{replace: ":id", to: "SJDUEB"},
{replace: ":docId", to: "12345"}
]
const uri = "https://abc.com/ :id /docs/ :docId /history";
function reduce(uri: string, option: any):string {
return uri.replace(option.replace, option.to);
}
const reult = Array.prototype.reduce.call(
replacementOptions, // list
reduce, // reducer
uri // initial value
)
在这种情况下,我们可以看到reducer函数根据列表(replacementOptions
)转换了初始值(uri
)
⚠️请注意,它并没有改变原列表数组。
案例4:编写一个简单的Redux实现
Redux是一个状态管理库,它具有全局状态对象,并且每一次操作状态都会被执行一次
reducer函数
。
const initialState = { sum:0, history:[] }
function reduce(state, action){
return {
sum: state.sum + action.n,
history: [...state.history, action.name]
}
}
const action1 = {
name: 'action1',
n: 1
};
const action2 = {
name: 'action2',
n: 2
};
const list = [action1, action2];
const finalState = Array.prototype.reduce.call(
list,
reduce,
initialState
);
console.log(finalState); {sum:3, history:['action1', 'action2']}
再一次,你可以看到我们只是转换了初始值(initial value),而不是列表list。
案例5: 将多个数据变成一个数据(参数完整使用到的案例)
-
callback
执行数组中每个值(如果没有提供initialValue
则第一个值除外)的函数,包含四个参数:accumulator
累加器累计回调的返回值
它是上一次调用回调时返回的累计值(或initialValue
)currentValue
数组中正在处理的元素currentIndex
数组中正在处理的当前元素的索引
可选。如果提供了initialValue
,则起始索引号为0,否则从索引1起始array
调用reduce()
的数组可选
initialValue
作为第一次调用callback
是的第一个参数的值。
可选。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce将报错
//输出为 {name:'Niko', age:25}
const keys = ['name', 'age'];
const values = ['Niko', 25];
keys.reduce(function(accumulator, currentValue, currentIndex){
accumulator[currentValue] = values[currentIndex];
return accumulator;
},{})
案例6: redux中的compose函数
compose
从右到左来组合多个函数
function compose(...funcs){
if(funcs.length === 0){
return arg => arg
}
if(funcs.length === 1){
return funcs[0]
}
return funcs.reduce((a,b)=> (...args) => a(b(...args)))
}