上次我寫了一篇簡單介紹函式式思維的文章,我們組的同學看了之後表示很感興趣,希望我有空多寫寫這方面的內容,然後表示他能想到用陣列的 map,但是想不到 reduce。我想這可能也是個普遍現象,因為在對 FP(函數語言程式設計)接觸不多的同學來講,腦海中對 map 的印象,可能基本等同於迴圈,而對 reduce 就相對陌生。但其實呢,reduce 是個比 map、flatMap 啥的更通用的函式,你可以用 reduce 輕易地實現其他函式。
我們先實現一下 reduce:
const reduce = (reducer, acc) => list => {
const [head, ...rest] = list;
if (head === undefined) return acc;
return reduce(reducer, reducer(acc, head))(rest);
};
複製程式碼
JS 裡 Array.prototype.reduce 跟我這個稍有不同,它的 reducer 可以接收四個引數(比我的版本多了 currentIndex 和 array),有 currentIndex 這個引數,就告訴我們它的實現大概率是通過迴圈做的,說實話個人感覺後面兩個引數基本是沒用的,其他語言裡的實現一般也沒這兩個引數。
然後再解釋下為啥我的 reduce 不是直接接收三個引數,而要用部分應用的形式,先接收兩個,返回一個接收一個引數的函式呢?是為了複用,我們看個例子,用 reduce 實現一個 sum 函式,把陣列裡的元素都累加起來:
const arr = [1, 2, 3]; // 下文所有的 arr 都是這個
const sum = reduce((acc, x) => acc + x, 0);
sum(arr); // => 6
複製程式碼
reduce 在接收不同的 reducer 和 acc 的時候會返回不同的函式,這裡是返回 sum,是不是就很順。而如果 reduce 是一個接收三個引數的函式,那 sum 就得是const sum = (arr) => reduce((acc, x) => acc + x, 0, arr)
,也不是不可以,但比較醜陋。
接下來我們用 reduce 實現陣列的其他方法:length、map、flatMap、includes、find
// JS 的 Array.length 跟我這個實現不一樣,
// arr[100] = 1,arr.length 就為 101 了,因為 JS 的 Array 本質是物件
const length = reduce(acc => acc + 1, 0);
length(arr); // => 3
const map = func => reduce((acc, x) => [...acc, func(x)], []);
map(x => x + 1)(arr); // => [2, 3, 4]
const flatMap = func => reduce((acc, x) => [...acc, ...func(x)], []);
flatMap(x => [x + 1])(arr); // => [2, 3, 4]
const includes = element => reduce((acc, x) => acc || (x === element), false);
includes(1)(arr); // => true
// 找到第一個符合條件的元素返回,否則返回 undefined
const find = func => reduce((acc, x) => acc || (func(x) ? x : undefined), undefined);
find(x => x > 2)(arr); // => 3
複製程式碼
我們有時候處理字串,也會想用到 reduce、map 啥的,那我們就給 String.prototype 加上:
// string
String.prototype.reduce = function (reducer, acc) {
return reduce(reducer, acc)(this.split(''));
};
String.prototype.map = function (func) {
return map(func)(this.split('')).join('');
};
const str = '123';
str.reduce((acc, x) => acc + Number(x), 0); // => 6
str.map(x => Number(x) + 1); // => '234'
複製程式碼
emmmmmmm……就這樣吧。