函數式編程中有一個比較重要的概念就是函數組合(compose),組合多個函數,同時返回一個新的函數。調用時,組合函數按順序從右向左執行。右邊函數調用後,返回的結果,做爲左邊函數的參數傳入,嚴格保證了執行順序,這也是compose 主要特色。javascript
compose 很是簡單,經過下面示例代碼,就很是清楚java
function compose (f, g) { return function(x) { return f(g(x)); } } var arr = [1, 2, 3], reverse = function(x){ return x.reverse()}, getFirst = function(x) {return x[0]}, compseFunc = compose(getFirst, reverse); compseFunc(arr); // 3
參數在函數間就好像經過‘管道’傳輸同樣,最右邊的函數接收外界參數,返回結果傳給左邊的函數,最後輸出結果。git
上面組合了兩個函數的compose,也讓咱們瞭解了組合的特色,接着咱們看看如何組合更多的函數,由於在實際應用中,不會像入門介紹的代碼那麼簡單。es6
主要注意幾個關鍵點:github
var compose = function() { var args = Array.prototype.slice.call(arguments); return function(x) { if (args.length >= 2) { return args.reverse().reduce((p, c) => { return p = c(p) }, x) } else { return args[1] && args[1](x); } } } // 利用上面示例 測試一下。 var arr = [1, 2, 3], reverse = function(x){ return x.reverse()}, getFirst = function(x) {return x[0]}, trace = function(x) { console.log('執行結果:', x); return x} compseFunc = compose(trace, getFirst, trace, reverse); compseFunc(arr); // 執行結果: (3) [3, 2, 1] // 執行結果: 3 // 3
如此實現,基本沒什麼問題,變量arr
在管道中傳入後,通過各類操做,最後返回告終果。編程
函數式編程(FP)裏面跟compose相似的方法,就是pipe。
pipe,主要做用也是組合多個函數,稱之爲'流', 確定得按照正常方法,從左往右調用函數,與compose 調用方法相反。redux
先看下compose 最基礎的兩參數版本,數組
const compose = (f1, f2) => value => f1(f2(value));
利用箭頭函數,很是直接的代表兩個函數嵌套執行的關係,框架
接着看多層嵌套。koa
(f1, f2, f3...) => value => f1(f2(f3));
抽象出來表示:
() => () => result;
先提出這些基礎的組合方式,對咱們後面理解高級es6方法實現compose有很大幫助。
前面提到pipe 是反向的compose,pipe正向調用也致使它實現起來更容易。
pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)
一行代碼就實現了pipe, 套用上面抽象出來的表達式,reduce
恰好正向遍歷全部函數, 參數x
做爲傳遞給函數的初始值, 後面每次f(v)
執行的結果,做爲下一次f(v)
調用的參數v
,完成了函數組合調用。
或者,能夠把函數組合中,第一個函數獲取參數後,獲得的結果,最爲reduce
遍歷的初始值。
pipe = (fn,...fns) => (x) => fns.reduce( (v, f) => f(v), fn(x));
利用es6提供的rest 參數 ,用於獲取函數的多餘參數.提取出第一個函數fn,多餘函數參數放到fns中,fns能夠當作是數組,也不用像arguments那種事先經過Array.prototype.slice.call
轉爲數組,arguments對性能損耗也能夠避免。 fn(x)
第一個函數執行結果做爲reduce
初始值。
pipe 部分,利用reduce實現,反過來看,compose
就能夠利用reduceRight
compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
利用遞歸
compose = (fn, ...fns) => fns.length === 0 ? fn: (...args) => fn(compose(...fns)(...args))
遞歸代碼,首先看出口條件, fns.length === 0
, 最後必定執行最左邊的函數,而後把剩下的函數再通過compose
調用,
利用reduce實現。
具體實現代碼點擊這裏,一行實現,並且仍是用正向的 reduce
。
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))
做者其實用例子作了解釋,能夠看下reduce
迭代的方向是從左往右的,而compose
要求執行的方向是從從右往左。對數組中每一項執行函數,正常狀況下都應該放回執行結果,好比(v, f) => f(v)
,返回f(v)
執行結果,這裏是(f, g) => (...args) => f(g(...args))
返回一個函數(...args) => f(g(...args))
,這樣就能夠保證後面的函數g
在被做爲參數傳入時比前面的函數f
先執行。
簡單利用前面的組合兩個函數的例子分析一下。
... composeFunc = compose(getFirst, trace, reverse); composeFunc(arr);
主要看reduce 函數裏面的執行過程:
(...args)=>getFirst(trace(...args))
做爲下一次迭代中累計器f
的值。第二次迭代,reduce函數中
f == (...args)=>getFirst(trace(...args)) g == reverse。 // 替換一下 (f, g) => (...args) => f(g(...args)) ((...args)=>getFirst(trace(...args)), reverse) => (...args) => ((...args)=>getFirst(trace(...args)))(reverse(...args))
迭代結束,最後獲得的comoseFunc就是
// 對照第二次的執行結果, (...args) => f(g(...args)) (...args) => ((...args)=>getFirst(trace(...args)))(reverse(...args))
調用函數composeFunc(arr)。
(arr) => ((...args)=>getFirst(trace(...args)))(reverse(arr)) ===》reverse(arr) 執行結果[3, 2, 1] 做爲參數 ((...args)=>getFirst(trace(...args)))([3,2,1]) ==》入參調用函數 getFirst(trace[3,2,1]) ===》 getFirst([3, 2, 1]) ===》 結果爲 3
很是巧妙的把後一個函數的執行結果做爲包裹着前面函數的空函數的參數,傳入執行。其中大量用到下面的結構
((arg)=> f(arg))(arg) // 轉換一下。 (function(x) { return f(x) })(x)
不管是compose, 仍是後面提到的pipe,概念很是簡單,均可以使用很是巧妙的方式實現(大部分使用reduce),並且在編程中很大程度上簡化代碼。最後列出優秀框架中使用compose的示例:
參考連接: