涅槃重生(二):JS中的curry化(柯里化)

真是個磨人的小妖精

為什么要用柯里化

柯里化(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ì)研讀,溫故而知新

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容