提到函數式編程,就不得不提柯里化和組合。說實話,在以前的項目開發中,對柯里化和組合的運用不是太多,由於不太清楚應該在哪些狀況下應該使用它們。因此在這篇文章中,咱們將詳細的介紹柯里化和組合的用法以及使用場景。前端
首先說說什麼是柯里化, 簡單來說就是部分應用, 也就是說 只傳遞函數的一部分參數來調用它,讓它返回一個函數去處理剩下的參數。面試
先來看個例子,建立一個 say
函數,打印出帶有名字,前綴和問候語的一句話。編程
const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`; say('Tom', 'Mr', 'Hello'); // "Hello, Mr Tom" say('James', 'Mr', 'Hello'); // "Hello, Mr James"
在上面的例子中,咱們每一次調用 say 函數都必須傳入完整的三個參數,才能保證正確的運行結果,不然,雖然程序仍是會正常運行,但是未傳入的部分會變成 undefined
。數組
利用柯里化,咱們能夠固定住其中的部分參數,在調用的時候,這個參數就至關於已經被記住了,不須要再次傳遞,也就是咱們這裏說的參數複用。app
const say = prefix => greeting => name => `${greeting}, ${prefix} ${name}!`; const sayToMr = say('Mr'); const sayToMiss = say('Miss'); const greetMr = sayToMr('Hello'); const greetMiss = sayToMiss('Hi'); greetMr('Tom'); // "Hi, Miss Cindy!" greetMiss('Cindy'); // "Hello, Mr Tom!"
這時候若是咱們想輸入相同的問候語 Hello, 咱們發現,原來的結構好像不太知足了呃,因而咱們開始調整參數的位置。函數式編程
const say = greeting => prefix => name => `${greeting}, ${prefix} ${name}!`; const greet = say('Hello'); const greetMeiNv = greet('美女'); const greetShuaiGe = greet('帥哥'); greetShuaiGe('Tom'); // "Hello, 帥哥 Tom!" greetMeiNv('Cindy'); // "Hello, 美女 Cindy!"
Note: 在使用柯里化的時候,參數的順序很重要,能夠考慮根據 易變化的程度來排列參數,把不容易變化的參數經過柯里化固定起來,將須要處理的參數放到最後一位。
在上面的例子中,經過柯里化,咱們竟然多造出了 3 個函數!簡直就是函數工廠嘛!可是猛地一想,若是若是是 100 個參數呢,難道要寫一百次?有沒有一種方法能夠簡單的幫咱們實現柯里化?函數
我要開始放書上的代碼了。學習
function curry(fn) { var outerArgs = Array.prototype.slice.call(arguments, 1); return function() { var innerArgs = Array.prototype.slice.call(arguments), finalArgs = outerArgs.concat(innerArgs); return fn.apply(null, finalArgs); }; } const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`; const curriedSay = curry(say); curriedSay('Tom', 'Mr', 'Hello'); // "Hello, Mr Tom!" curry(say,'Tom', 'Mr')('Hello'); // "Hello, Mr Tom!"
簡單解釋一下上面的代碼,首先是獲得除了第一個參數 fn
以外的全部的外部傳參 outerArgs
,這裏的 arguments 是一個長得像數組的對象,因此咱們要使用 Array.proptype.slice
將其轉變成真正的數組。 innerArgs
用來獲取調用這個匿名函數時的傳參。最後將外部傳參 outerArgs
和內部傳參 innerArgs
合併,調用 fn。也就是說這時 fn 才被調用。prototype
就比如刷信用卡和儲蓄卡,刷儲蓄卡就是把你的錢立刻轉到別人口袋,刷信用卡是銀行先幫你墊着,到下個月再把錢還給銀行。總之,最後都是花本身的錢。不過這樣有一個好處就是,就是可讓你養成拆分函數,並給函數良好命名的習慣,以及更好的處理和抽象代碼的邏輯。code
固然,你也能夠可使用 lodash
或者 ramda
這樣的庫來快速柯里化你的函數,這樣能夠省去不少重複造輪子的工做。
下面以使用 lodash
爲例。
const say = (prefix, name, greeting) => `${greeting}, ${prefix} ${name}!`; const curreiedSay = _.curry(say); curreiedSay('Mr','Tom','Hello'); // "Hello, Mr Tom!" curreiedSay('Mr')('Tom','Hello'); // "Hello, Mr Tom!" curreiedSay('Mr')('Tom')('Hello'); // "Hello, Mr Tom!" curreiedSay('Tom')(_,'Hello')('Mr'); // "Hello, Mr Tom!"
lodash 和 Ramda 都提供了一系列柯里化函數的包裝方法,感興趣的同窗能夠打開 lodash / ramda 官網,在 console 裏面試一下。
組合,顧名思義,也就是把多個函數組合起來變成一個函數。
const compose = (fn1, fn2) => args => fn1(fn2(args)); const toUpperCase = value => value.toUpperCase(); const addSuffix = value => `${value} is good!`; const format = compose(toUpperCase, addSuffix); format('apple'); // "APPLE IS GOOD!"
上面的例子中,fn2 先執行,而後將返回值做爲 fn1 的參數,因此 compose 裏面的方法是從右向左
執行的。就像一條流水線同樣,a 流水線先把汽車組裝好,而後交給 b 流水線進行噴漆,再交給 c 流水線打磨等等,最後獲得一輛嶄新的汽車。
學習完柯里化和組合以後,讓咱們將它們結合起來使用,必定可以碰撞出更強的火花,產生更大的威力。
說寫就寫。
假設有一個數組,咱們指望先對數組進行去重,而後對數組進行求和或求積。
const unique = arr => _.uniq(arr); // 數組去重 const sum = arr => _.reduce(arr, (total, n) => total + n); // 數組元素的累加之和 const multiply = arr => _.reduce(arr, (total, n) => total * n); // 數組元素的乘積 const getTotal = fn => arr => _.flowRight(fn, unique)(arr); // 從右至左, 先去重, 再執行 fn const arr1 = [1, 2, 3, 4, 4, 5, 5]; const arr2 = [1, 2, 2, 3, 4, 4, 5]; const getSumTotal = getTotal(sum); // 經過柯里化產生一個新的函數 const getMultiplyTotal = getTotal(multiply); // 經過柯里化產生一個新的函數 getSumTotal(arr1); // 15 getMultiplyTotal(arr2); // 120
如今的前端社區中,函數式編程隨處可見,柯里化和組合也成爲了咱們必須掌握的技能。在項目開發中,能夠不斷的去增強練習。