在說compose函數(shù)之前,我們先來看一道題目:

Your task is to write a higher order function for chaining together a list of unary functions. In other words, it should return a function that does a left fold on the given functions.
chained([a,b,c,d])(input)
Should yield the same result as
d(c(b(a(input))))
大致意思就是寫個(gè)函數(shù) 能將多個(gè)函數(shù)進(jìn)行組合成一個(gè)函數(shù),就是一元鏈?zhǔn)胶瘮?shù)
思考
當(dāng)時(shí)我想,要想實(shí)現(xiàn)一個(gè)這樣的函數(shù),肯定是需要有一個(gè)遍歷的過程,一個(gè)函數(shù)的執(zhí)行結(jié)果是另一個(gè)函數(shù)的參數(shù),那這樣就需要有個(gè)累計(jì)的過程,綜合以上2點(diǎn),想到數(shù)組中有個(gè)reduce函數(shù)。這里我們來看看reduce函數(shù):
arr.reduce(callback[, initialValue])
其中 callback是執(zhí)行數(shù)組中每個(gè)值的函數(shù),它包含四個(gè)參數(shù):
accumulator 累加器累加回調(diào)的返回值; 它是上一次調(diào)用回調(diào)時(shí)返回的累積值,或initialValue(如下所示)。
currentValue 數(shù)組中正在處理的元素。
currentIndex[可選] 數(shù)組中正在處理的當(dāng)前元素的索引。 如果提供了initialValue,則索引號(hào)為0,否則為索引為1。
array[可選] 調(diào)用reduce的數(shù)組
initialValue
[可選] 用作第一個(gè)調(diào)用 callback的第一個(gè)參數(shù)的值。 如果沒有提供初始值,則將使用數(shù)組中的第一個(gè)元素。 在沒有初始值的空數(shù)組上調(diào)用 reduce 將報(bào)錯(cuò)。
下面的例子求數(shù)組成員之和。
[1, 2, 3, 4, 5].reduce(function(x, y){
console.log(x, y)
return x + y;
});
// 1 2
// 3 3
// 6 4
// 10 5
//最后結(jié)果:15
上面代碼中,第一輪執(zhí)行,x是數(shù)組的第一個(gè)成員,y是數(shù)組的第二個(gè)成員。從第二輪開始,x為上一輪的返回值,y為當(dāng)前數(shù)組成員,直到遍歷完所有成員,返回最后一輪計(jì)算后的x。
利用reduce方法,可以寫一個(gè)數(shù)組求和的sum方法。
Array.prototype.sum = function (){
return this.reduce(function (pre, next) {
return pre + next;
})
};
[3, 4, 5, 6, 10].sum()
// 28
如果要對(duì)累積變量指定初值,可以把它放在reduce方法的第二個(gè)參數(shù)。
[1, 2, 3, 4, 5].reduce(function(x, y){
return x + y;
}, 10);
// 25
上面代碼指定參數(shù)x的初值為10,所以數(shù)組從10開始累加,最終結(jié)果為25。注意,這時(shí)y是從數(shù)組的第一個(gè)成員開始遍歷。
第二個(gè)參數(shù)相當(dāng)于設(shè)定了默認(rèn)值,處理空數(shù)組時(shí)尤其有用。
function add(prev, cur) {
return prev + cur;
}
[].reduce(add)
// TypeError: Reduce of empty array with no initial value
[].reduce(add, 1)
// 1
上面代碼中,由于空數(shù)組取不到初始值,reduce方法會(huì)報(bào)錯(cuò)。這時(shí),加上第二個(gè)參數(shù),就能保證總是會(huì)返回一個(gè)值。
解決
在上面我們了解學(xué)習(xí)reduce之后,我們可以開始來解決這道題了,看代碼:
function chained(funcs) {
return function(input){
return funcs.reduce(function(input, fn){ return fn(input) }, input);
}
}
驗(yàn)證一下
function f1(x){ return x*2 }
function f2(x){ return x+2 }
function f3(x){ return Math.pow(x,2) }
function f4(x){ return x.split("").concat().reverse().join("").split(" ")}
function f5(xs){ return xs.concat().reverse() }
function f6(xs){ return xs.join("_") }
Test.assertEquals( chained([f1,f2,f3])(0), 4 )
Test.assertEquals( chained([f1,f2,f3])(2), 36 )
Test.assertEquals( chained([f3,f2,f1])(2), 12 )
Test.assertEquals(chained([f4,f5,f6])("lorem ipsum"), "merol_muspi")

嗯,很好,驗(yàn)證通過的,沒什么問題!
進(jìn)一步思考
問題是解決了,但是認(rèn)真想想一下,這里的題目的需求是不是和redux中compose函數(shù)實(shí)現(xiàn)的需求一樣呢,嗯,我們來看看compose是怎么實(shí)現(xiàn)的。
https://github.com/reactjs/redux/blob/v3.7.2/src/compose.js
export default 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)))
}
用es6寫的,關(guān)于es6的知識(shí),可以看 ECMAScript 6 入門或者es6的十大特性
參考這個(gè)compose函數(shù)的寫法,我們來解一下上面那道題,
function chained(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
驗(yàn)證一下:
function f1(x){ return x*2 }
function f2(x){ return x+2 }
function f3(x){ return Math.pow(x,2) }
function f4(x){ return x.split("").concat().reverse().join("").split(" ")}
function f5(xs){ return xs.concat().reverse() }
function f6(xs){ return xs.join("_") }
Test.assertEquals( chained(f1,f2,f3)(0), 4 )
Test.assertEquals( chained(f1,f2,f3)(2), 36 )
Test.assertEquals( chained(f3,f2,f1)(2), 12 )
Test.assertEquals(chained(f4,f5,f6)("lorem ipsum"), "merol_muspi")

waht? 怎么會(huì)只通過一個(gè),原來是順序反了,題目要求是從右到左累計(jì)的,所以這里我們就需要用到reduce函數(shù)的兄弟函數(shù)reduceRight了,關(guān)于兩者的區(qū)別,
reduce是從左到右處理(從第一個(gè)成員到最后一個(gè)成員),reduceRight則是從右到左(從最后一個(gè)成員到第一個(gè)成員),其他完全一樣。
最終代碼:
function chained(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduceRight((a, b) => (...args) => a(b(...args)))
}
或者
function chained(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => b(a(...args)))
}
嗯,完美。