摘要:提到函數(shù)式編程,就不得不提柯里化和組合。說實話,在之前的項目開發(fā)中,對柯里化和組合的運用不是太多,因為不太清楚應(yīng)該在哪些情況下應(yīng)該使用它們。所以在這篇文章中,我們將詳細(xì)的介紹柯里化和組合的用法以及使用場景。
提到函數(shù)式編程,就不得不提柯里化和組合。說實話,在之前的項目開發(fā)中,對柯里化和組合的運用不是太多,因為不太清楚應(yīng)該在哪些情況下應(yīng)該使用它們。所以在這篇文章中,我們將詳細(xì)的介紹柯里化和組合的用法以及使用場景。
柯里化 Curry首先說說什么是柯里化, 簡單來講就是部分應(yīng)用, 也就是說 只傳遞函數(shù)的一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)。
參數(shù)復(fù)用先來看個例子,創(chuàng)建一個 say 函數(shù),打印出帶有名字,前綴和問候語的一句話。
const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`; say("Tom", "Mr", "Hello"); // "Hello, Mr Tom" say("James", "Mr", "Hello"); // "Hello, Mr James"
在上面的例子中,我們每一次調(diào)用 say 函數(shù)都必須傳入完整的三個參數(shù),才能保證正確的運行結(jié)果,否則,雖然程序還是會正常運行,可是未傳入的部分會變成 undefined。
利用柯里化,我們可以固定住其中的部分參數(shù),在調(diào)用的時候,這個參數(shù)就相當(dāng)于已經(jīng)被記住了,不需要再次傳遞,也就是我們這里說的參數(shù)復(fù)用。
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, 我們發(fā)現(xiàn),原來的結(jié)構(gòu)好像不太滿足了呃,于是我們開始調(diào)整參數(shù)的位置。
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: 在使用柯里化的時候,參數(shù)的順序很重要,可以考慮根據(jù)易變化的程度來排列參數(shù),把不容易變化的參數(shù)通過柯里化固定起來,將需要處理的參數(shù)放到最后一位。延遲執(zhí)行
在上面的例子中,通過柯里化,我們居然多造出了 3 個函數(shù)!簡直就是函數(shù)工廠嘛!但是猛地一想,如果如果是 100 個參數(shù)呢,難道要寫一百次?有沒有一種方法可以簡單的幫我們實現(xiàn)柯里化?
我要開始放書上的代碼了。
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!"
簡單解釋一下上面的代碼,首先是得到除了第一個參數(shù) fn 之外的所有的外部傳參 outerArgs,這里的 arguments 是一個長得像數(shù)組的對象,所以我們要使用 Array.proptype.slice 將其轉(zhuǎn)變成真正的數(shù)組。 innerArgs 用來獲取調(diào)用這個匿名函數(shù)時的傳參。最后將外部傳參 outerArgs 和內(nèi)部傳參 innerArgs 合并,調(diào)用 fn。也就是說這時 fn 才被調(diào)用。
就好比刷信用卡和儲蓄卡,刷儲蓄卡就是把你的錢馬上轉(zhuǎn)到別人口袋,刷信用卡是銀行先幫你墊著,到下個月再把錢還給銀行。總之,最后都是花自己的錢。不過這樣有一個好處就是,就是可以讓你養(yǎng)成拆分函數(shù),并給函數(shù)良好命名的習(xí)慣,以及更好的處理和抽象代碼的邏輯。
使用 Ramda / Lodash 生成柯里化函數(shù)當(dāng)然,你也可以可以使用 lodash 或者 ramda 這樣的庫來快速柯里化你的函數(shù),這樣可以省去很多重復(fù)造輪子的工作。
下面以使用 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 都提供了一系列柯里化函數(shù)的包裝方法,感興趣的同學(xué)可以打開 lodash / ramda 官網(wǎng),在 console 里面試一下。組合 Compose
組合,顧名思義,也就是把多個函數(shù)組合起來變成一個函數(shù)。
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 先執(zhí)行,然后將返回值作為 fn1 的參數(shù),所以 compose 里面的方法是從右向左執(zhí)行的。就像一條流水線一樣,a 流水線先把汽車組裝好,然后交給 b 流水線進(jìn)行噴漆,再交給 c 流水線打磨等等,最后得到一輛嶄新的汽車。
結(jié)合柯里化和組合 Curry + Compose學(xué)習(xí)完柯里化和組合之后,讓我們將它們結(jié)合起來使用,一定能夠碰撞出更強(qiáng)的火花,產(chǎn)生更大的威力。
說寫就寫。
假設(shè)有一個數(shù)組,我們期望先對數(shù)組進(jìn)行去重,然后對數(shù)組進(jìn)行求和或求積。
const unique = arr => _.uniq(arr); // 數(shù)組去重 const sum = arr => _.reduce(arr, (total, n) => total + n); // 數(shù)組元素的累加之和 const multiply = arr => _.reduce(arr, (total, n) => total * n); // 數(shù)組元素的乘積 const getTotal = fn => arr => _.flowRight(fn, unique)(arr); // 從右至左, 先去重, 再執(zhí)行 fn const arr1 = [1, 2, 3, 4, 4, 5, 5]; const arr2 = [1, 2, 2, 3, 4, 4, 5]; const getSumTotal = getTotal(sum); // 通過柯里化產(chǎn)生一個新的函數(shù) const getMultiplyTotal = getTotal(multiply); // 通過柯里化產(chǎn)生一個新的函數(shù) getSumTotal(arr1); // 15 getMultiplyTotal(arr2); // 120
現(xiàn)在的前端社區(qū)中,函數(shù)式編程隨處可見,柯里化和組合也成為了我們必須掌握的技能。在項目開發(fā)中,可以不斷的去加強(qiáng)練習(xí)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91134.html
摘要:函數(shù)式編程,一看這個詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...
摘要:組合的概念是非常直觀的,并不是函數(shù)式編程獨有的,在我們生活中或者前端開發(fā)中處處可見。其實我們函數(shù)式編程里面的組合也是類似,函數(shù)組合就是一種將已被分解的簡單任務(wù)組織成復(fù)雜的整體過程。在函數(shù)式編程的世界中,有這樣一種很流行的編程風(fēng)格。 JavaScript函數(shù)式編程,真香之認(rèn)識函數(shù)式編程(一) 該系列文章不是針對前端新手,需要有一定的編程經(jīng)驗,而且了解 JavaScript 里面作用域,閉...
摘要:函數(shù)式編程逐漸被邊緣化,被拋棄到學(xué)術(shù)界和非主流的場外。組合式編程的重新崛起年左右,有個巨大的變化爆發(fā)了。人們開始逐漸在私下里談?wù)摵瘮?shù)式編程。箭頭函數(shù)對于函數(shù)式編程的爆發(fā)起到了推動劑的作用?,F(xiàn)在很少看到那種不用函數(shù)式編程的大型應(yīng)用了。 showImg(https://segmentfault.com/img/remote/1460000009036867?w=800&h=364); 本...
摘要:本書主要探索函數(shù)式編程的核心思想。我們在中應(yīng)用的僅僅是一套基本的函數(shù)式編程概念的子集。我稱之為輕量級函數(shù)式編程。通常來說,關(guān)于函數(shù)式編程的書籍都熱衷于拓展閱讀者的知識面,并企圖覆蓋更多的知識點。,本書統(tǒng)稱為函數(shù)式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 譯者團(tuán)隊(排名不分先后)...
摘要:但是,對函數(shù)式編程而言,這個行為的重要性是毋庸置疑的。關(guān)于該模式更正式的說法是偏函數(shù)嚴(yán)格來講是一個減少函數(shù)參數(shù)個數(shù)的過程這里的參數(shù)個數(shù)指的是希望傳入的形參的數(shù)量。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅實的梁柱;分享,是...
閱讀 1520·2021-08-09 13:47
閱讀 2777·2019-08-30 15:55
閱讀 3504·2019-08-29 15:42
閱讀 1125·2019-08-29 13:45
閱讀 3019·2019-08-29 12:33
閱讀 1752·2019-08-26 11:58
閱讀 995·2019-08-26 10:19
閱讀 2419·2019-08-23 18:00