概述
一直以來都在函數式編程的大門之外徘徊,要入門的話首先得熟悉各種高階函數,數組的reduce方法就是其中之一。
reduce方法將會對數組元素從左到右依次執行reducer函數,然后返回一個累計的值。舉個形象的例子:你要組裝一台電腦,買了主板、CPU、顯卡、內存、硬盤、電源...這些零件是組裝電腦的必要條件。
裝的過程可以簡單概括為拆掉每個零件的包裝,再裝到一起。類比一下reduce函數就可以明白了,那些零件就相當於要執行reduce方法的數組,對每個零件執行拆除包裝的加工程序,就是對數組的每個元素執行reducer函數,把這些零件裝到一起,就相當於reduce方法的任務,最終組裝好的電腦相當於reduce方法的返回值。
reduce方法接收兩個參數,第一個參數是回調函數reducer,第二個參數是初始值。reducer函數接收四個參數:
- Accumulator:MDN上解釋為累計器,但我覺得不恰當,按我的理解它應該是截至當前元素,之前所有的數組元素被reducer函數處理累計的結果
- Current:當前被執行的數組元素
- CurrentIndex: 當前被執行的數組元素索引
- SourceArray:原數組,也就是調用reduce方法的數組
如果傳入第二個參數,reduce方法會在這個參數的基礎上開始累計執行。
概念講了那么多,那reduce它的執行機制是怎樣的呢?別着急,從用法入手一點一點分析。
來個最好理解的例子:數組求和
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函數。
總共執行了三次,得出最終結果。那如果傳入初始值,是怎樣的執行順序?
console.log(arr.reduce(accumulator, 3))
結果如下:
這次是以傳入的初始值作為累加的起點,然后依次對數組的元素執行reducer。
假設對沒有初始值的空數組調用reduce方法,則會報錯:
Uncaught TypeError: Reduce of empty array with no initial value
一些用法
講了一些概念,但使用場景更重要,下面來看一下reduce方法都會有哪些用途。
compose函數
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])
- 第二次執行,reduce的回調參數a是上一次回調執行的結果[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
這種方式實際上處理的是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); })
資源搜索網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com
實現
通過上面的用法,可以總結出來reduce的特點:
- 接收兩個參數,第一個為函數,函數內會接收四個參數:Accumulator Current CurrentIndex SourceArray,第二個參數為初始值。
- 返回值為一個所有Accumulator累計執行的結果
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 }