一直以来都在函数式编程的大门以外徘徊,要入门的话首先得熟悉各类高阶函数,数组的reduce方法就是其中之一。前端
reduce方法将会对数组元素从左到右依次执行reducer函数,而后返回一个累计的值。举个形象的例子:你要组装一台电脑,买了主板、CPU、显卡、内存、硬盘、电源...这些零件是组装电脑的必要条件。编程
装的过程能够简单归纳为拆掉每一个零件的包装,再装到一块儿。类比一下reduce函数就能够明白了,那些零件就至关于要执行reduce方法的数组,对每一个零件执行拆除包装的加工程序,就是对数组的每一个元素执行reducer函数,把这些零件装到一块儿,就至关于reduce方法的任务,最终组装好的电脑至关于reduce方法的返回值。redux
reduce方法接收两个参数,第一个参数是回调函数reducer,第二个参数是初始值。reducer函数接收四个参数:数组
若是传入第二个参数,reduce方法会在这个参数的基础上开始累计执行。promise
概念讲了那么多,那reduce它的执行机制是怎样的呢?别着急,从用法入手一点一点分析。app
来个最好理解的例子:数组求和koa
const arr = [1, 2, 3, 4] const accumulator = (total, current, currentIndex, arr) => { console.log(total, current, currentIndex, arr); return total + current } console.log(arr.reduce(accumulator))
执行结果以下:函数式编程
很明确,最终的结果就是把全部数组的元素都加起来。值得注意的是,它将数组的第一个元素做为累加的初始值,而后再依次对后边的元素执行reducer函数。函数
总共执行了三次,得出最终结果。那若是传入初始值,是怎样的执行顺序?this
console.log(arr.reduce(accumulator, 3))
结果以下:
此次是以传入的初始值做为累加的起点,而后依次对数组的元素执行reducer。
假设对没有初始值的空数组调用reduce方法,则会报错:
Uncaught TypeError: Reduce of empty array with no initial value
讲了一些概念,但使用场景更重要,下面来看一下reduce方法都会有哪些用途。
compose是函数式编程的一种形式,用于将多个函数合并,上一个函数的返回值做为当前函数的入参,当前函数的返回值再做为下一个函数的入参,这样的效果有点相似于koa中间件的洋葱模型。
[a, b, c, d]
=> a(b(c(d())))
实际上和累加差很少,只不过把累加操做变成了入参执行,相加的结果变成了执行的返回值。redux的applyMiddleware内就使用了compose,目的是保证最终的dispatch是被全部中间件处理后的结果。
下面来以applyMiddleware中的compose为例,先看用法:
const result = compose(a, b, c)(params)
执行状况是这样:
(params) => a(b(c(params)))
返回的是一个函数,将params做为该函数的入参,被右侧第一个函数执行,执行顺序是从右到左执行,其他的函数的参数都是上一个函数的返回值。
看一下实现:
function compose(...funcs) { // funcs是compose要组合的那些函数,arg是componse返回的函数的参数 if (funcs.length === 0) { // 若是没有传入函数,那么直接返回一个函数,函数的返回值就是传进来的参数 return arg => arg } if (funcs.length === 1) { // 若是只传入了一个函数,直接返回这个函数 return funcs[0] } return funcs.reduce((all, current) => { return (...args) => { return all(current(...args)) } }) }
const array = [[0, 1], [2, 3], [4, 5]] const flatten = arr => { return arr.reduce((a, b) => { return a.concat(b) }, []) } console.log(flatten(array)); // [0, 1, 2, 3, 4, 5]
来看一下执行过程,
[]
,走到reduce的回调里,参数a就这个[]
,参数b是数组第一项[0, 1]
,回调内[].cancat([0, 1])
[0, 1]
,本次继续用它来concat数组的第二项[2, 3]
,获得结果[0, 1, 2, 3]
做为下一次回调执行的参数a继续执行下去那么假设数组是这样呢?[[0, [111, 222], 1], [2, [333, [444, 555]], 3], [4, 5]]
,其实加个递归调用就能够
const array = [[0, [111, 222], 1], [2, [333, [444, 555]], 3], [4, 5]] const flatten = arr => { return arr.reduce((a, b) => { if (b instanceof Array) { return a.concat(flatten(b)) } return a.concat(b) }, []) } console.log(flatten(array)); // [0, 111, 222, 1, 2, 333, 444, 555, 3, 4, 5]
每次回调执行的时候,都会往对象中加一个key为字符串,value为出现次数的键值,若已经存储过字符串,那么它的value加1。
const str = 'adefrfdnnfhdueassjfkdiskcddfjds' const arr = str.split('') const strObj = arr.reduce((all, current) => { if (current in all) { all[current]++ } else { all[current] = 1 } return all }, {}) console.log(strObj) // {"a":2,"d":7,"e":2,"f":5,"r":1,"n":2,"h":1,"u":1,"s":4,"j":2,"k":2,"i":1,"c":1}
const arr = ['1', 'a', 'c', 'd', 'a', 'c', '1'] const afterUnique = arr.reduce((all, current) => { if (!all.includes(current)) { all.push(current) } return all }, []) console.log(afterUnique); // ["1", "a", "c", "d"]
这种方式实际上处理的是promise的value,将上一个promise的value做为下一个promise的value进行处理。
const prom1 = a => { return new Promise((resolve => { resolve(a) })) } const prom2 = a => { return new Promise((resolve => { resolve(a * 2) })) } const prom3 = a => { return new Promise((resolve => { resolve(a * 3) })) } const arr = [prom1, prom2, prom3] const result = arr.reduce((all, current) => { return all.then(current) }, Promise.resolve(10)) result.then(res => { console.log(res); })
经过上面的用法,能够总结出来reduce的特色:
Array.prototype.myReduce = function(fn, base) { if (this.length === 0 && !base) { throw new Error('Reduce of empty array with no initial value') } for (let i = 0; i < this.length; i++) { if (!base) { base = fn(this[i], this[i + 1], i, this) i++ } else { base = fn(base, this[i], i, this) } } return base }
更多技术文章能够关注公众号:一口一个前端