
為什么要用柯里化
柯里化(Currying,下文都使用中文譯名)對我來說其實并不能說是很陌生,在大大小小的各種筆試面試中總能瞥見它的身影。但一直以來都處于懵懵懂懂的階段,剛好最近有了這么一大段的空閑時間,不如就利用起來專心補上那些自己知識框架上的漏洞。
在函數(shù)式編程的世界中,柯里化有著舉足輕重的地位,我的一位老友評價這樣評價:柯里化是函數(shù)式編程思想實現(xiàn)的高鐵方式。那么柯里化究竟是什么呢?
柯里化是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)(維基百科抄來的)
怎么柯里化
從概念上來看好像有些拗口,那我們對它抽絲剝繭一下,其實也就是允許只傳遞一部分參數(shù)調(diào)用它(一般為1個參數(shù)),同時返回一個能繼續(xù)處理剩下參數(shù)的函數(shù)。
還是看不懂?我覺得張鑫旭童鞋舉得一夫一妻制的例子還是很貼切的。
傳送門:https://www.zhangxinxu.com/wordpress/2013/02/js-currying/
我們來看一個返回兩個數(shù)之和的普通函數(shù)的例子
//普通函數(shù)
function add(x, y) {
return x + y;
}
這個函數(shù)是我們在各類JS入門書籍中常見的標(biāo)準(zhǔn)函數(shù)定義式,但其很明顯不符合柯里化的性質(zhì),所以在這里我們用眼里把它改寫成符合柯里化性質(zhì)的函數(shù):
//符合柯里化的函數(shù)
var add = function(x) {
return functin(y) {
return x + y;
}
}
//檢查是否為柯里化
add(1)(4) //5
add(2)(5) === 7 //true
var inc = add(2);
inc(1) //3
這里其實利用了閉包的性質(zhì),從而在最里層處理時能獲得前面的參數(shù)。由以上檢驗條件可知我們的這種寫法的確滿足柯里化的條件。
但是這么寫只是對于柯里化的淺層次的理解,我們需要為遇到的每個問題編寫一個特殊性的函數(shù),也就是該函數(shù)只能適用于特定的場景,顯然這并不能我們追求的最終目標(biāo)。
那我們應(yīng)該怎樣對它進行改造呢?
我想首先應(yīng)該是從這一個特殊函數(shù)的抽象表達(dá)開始:
//我們將通用性的函數(shù)命名為currying
//根據(jù)柯里化函數(shù)調(diào)用特點其應(yīng)該接收一個函數(shù)和剩余參數(shù)
//我們設(shè)函數(shù)為fn,arg為剩余參數(shù)
function currying (fn, ...args1){
return function (...args2) {
//最里層,...arg表示所有參數(shù)
return fn(...args1, ...args2)
}
}
從這個例子可以看出來,所謂的柯里化其實就是要讓函數(shù)理解并處理其中的部分應(yīng)用,那么如果我們想要達(dá)到這個目的就有以下兩個需求需要完成:
- 將傳入的參數(shù)剝離開來
- 返回函數(shù)也應(yīng)該柯里化
道理我都懂,可是具體實現(xiàn)究竟長什么樣的呢?
別急,好菜馬上就來啦~
//ES6版本
function currying(fn, ...args) {
if(args.length >= fn.length) {
//判斷返回值是否需要柯里化,若參數(shù)達(dá)到執(zhí)行條件則返回原函數(shù)
return fn(...args);
}
return function(...args){
return currying(fn, ...args, ...args2)
}
}
//ES5版本
//ES5并沒有方便的...用法,所以我們考慮用apply和concat達(dá)到目的
//值得注意這里 [].slice.call(arguments)的用法能將有l(wèi)ength屬性的對象轉(zhuǎn)成數(shù)組
//相當(dāng)于Array.prototype.slice.call(arguments)
function currying(fn) {
//fn指的是處理函數(shù)
//args存儲已處理的參數(shù)
var args = [].slice.call(arguments, 1);
return function() {
//將新處理的跟已處理的合起來
var new = args.concat([].slice.call(arguments));
return fn.apply(null, new);
}
}
柯里化的使用場景
每次學(xué)到新技術(shù)的時候,心底不由得升起一個大大的問號,想我當(dāng)年身在江湖,洛必達(dá)洛到底,jquery用到底也不見會出什么問題???
在很多文檔里都會告訴大家,柯里化有三個常見的作用,分別是:
- 參數(shù)復(fù)用
- 提前返回
- 延遲執(zhí)行
參數(shù)復(fù)用
都試了一遍我還是覺得這個作用是最大的,不信我們來擼個小李子看看:
//還是沿用上面加法的例子
var newAdd = currying(add, 10);
newAdd(20); //30
newAdd(30): //40
這樣我們就可以實現(xiàn)固定了一個參數(shù)10,而不必反復(fù)進行書寫。
提前返回
我們來看這么一個例子
//假設(shè)我們需要統(tǒng)計紅色蘋果數(shù)量,且無論顏色都需經(jīng)過二次加工
var count.do = function(type,fn){//處理函數(shù)略}
var doColor = function(count, fn, capture) {
if(apple.color === "red") {
count.do("type1", fn, (capture));
} else if (apple.color === "green") {
count.do("type2", fn, (capture));
}
}
這里的問題在于,每次的判斷中if...else if...都要走一遍,其實我們利用柯里化只需要走一遍即可,只需簡單改造:
var count.do = function(type,fn){//處理函數(shù)略}
var doColor = (function(){
if(apple.color === "red"){
return function(count, fn, capture){
count.do("type1", fn, (capture));
}
}else if (apple.color === "green"){
return function(count, fn, capture){
count.do("type2", fn, (capture));
}
}
})
這樣執(zhí)行doColor其實只執(zhí)行了判斷,其他部分的執(zhí)行放到了return的回調(diào)函數(shù)里了。
這樣就實現(xiàn)了柯里化啦~是不是很有用的感覺。
延遲執(zhí)行
柯里化的實現(xiàn)跟bind()這個方法的實現(xiàn)原理類似,這部分一直沒發(fā)現(xiàn)有什么非用不可的使用場景~所以這部分先空出來等待日后補充吧。
結(jié)語
一路至此柯里化的基本概念也已梳理的差不多了,有關(guān)于邏輯的難點從來都不是難在看懂示例,而是把學(xué)過的模式應(yīng)用于我們的實際工程中,任重而道遠(yuǎn)。
本人比較喜歡的lodash庫對于柯里化也已提供了支持,想完全搞懂可以細(xì)細(xì)研讀該庫源碼的柯里化部分(恕小生愚笨,有些地方我也還沒能完全看懂)
柯里化是近年來各廠喜歡的筆面試題之一,所以還需細(xì)細(xì)研讀,溫故而知新