函數(shù)柯里化小結(jié)

最近學(xué)習(xí)了一下高階函數(shù),其中印象比較深刻的有函數(shù)消抖,函數(shù)節(jié)流以及函數(shù)柯里化(curry),在學(xué)習(xí)中也是從一頭霧水到逐漸明了,所以寫一篇文章對(duì)柯里化進(jìn)行總結(jié)。

什么是函數(shù)柯里化

javascript忍者中說:在一個(gè)函數(shù)中首先填充幾個(gè)參數(shù)(然后再返回一個(gè)新函數(shù))的技術(shù)稱為柯里化(Currying)。聽起來跟bind的作用是一樣的,其實(shí)bind也可以采用這種思想來實(shí)現(xiàn)(至于bind原本是怎么實(shí)現(xiàn)的,我不清楚,控制臺(tái)輸出Function.prototype.bind,輸出是[native code],看不到,不清楚內(nèi)部原理)。
在很多文章中寫到:柯里化通常也稱部分求值,其含義是給函數(shù)分步傳遞參數(shù),每次傳遞參數(shù)后部分應(yīng)用參數(shù),并返回一個(gè)更具體的函數(shù)接受剩下的參數(shù),這中間可嵌套多層這樣的接受部分參數(shù)函數(shù),直至返回最后結(jié)果。
舉個(gè)不是很恰當(dāng)?shù)睦?,有一個(gè)廚師,要做飯,但是餐館的小兒沒有把菜買齊,這樣,小兒買一份原料,放在廚師廚房,再買一份,放在廚師廚房,等買齊了,叫廚師過來,好了,原料齊了,可以做飯了。這個(gè)時(shí)候,廚師利用原料,把飯做好。廚師就像一個(gè)函數(shù),他有自己的功能(做飯),但是參數(shù)(原料)不齊,每次執(zhí)行這個(gè)函數(shù),在參數(shù)不齊的情況下,只能返回一個(gè)新的函數(shù),這個(gè)新的函數(shù)已經(jīng)內(nèi)置了之前的參數(shù),當(dāng)參數(shù)齊了之后完成他本身的功能。

問題

要實(shí)現(xiàn)一個(gè)這樣的加法函數(shù),使得:

add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() === 42

首先,可以看到,這個(gè)函數(shù),只有當(dāng)參數(shù)為空的時(shí)候,才執(zhí)行之前所有數(shù)值的加法,這樣的嵌套可以無限進(jìn)行,當(dāng)有參數(shù)的時(shí)候,add(1,2,3),這個(gè)時(shí)候的返回值應(yīng)該是一個(gè)函數(shù),這個(gè)函數(shù)存儲(chǔ)了1,2,3但是沒有執(zhí)行加法(執(zhí)行了也行,此處假設(shè)就不執(zhí)行,只是起到保存參數(shù)的作用),這樣,繼續(xù)執(zhí)行add(1,2,3)(2)()就能輸出1+2+3+2=8。
要實(shí)現(xiàn)這樣的一個(gè)函數(shù),首先,返回值在參數(shù)不為空的時(shí)候必定返回一個(gè)函數(shù),該函數(shù)還保存了之前的參數(shù),這就需要用到閉包。
最終的實(shí)現(xiàn)如下:

// add 函數(shù)柯里化
function add(){
    //建立args,利用閉包特性,不斷保存arguments
    var args = [].slice.call(arguments);
       //方法一,新建_add函數(shù)實(shí)現(xiàn)柯里化
    var _add = function(){
        if(arguments.length === 0){
            //參數(shù)為空,對(duì)args執(zhí)行加法
            return args.reduce(function(a,b){return a+b});
        }else {
            //否則,保存參數(shù)到args,返回一個(gè)函數(shù)
            [].push.apply(args,arguments);
            return _add;
        }
    }
    //返回_add函數(shù)
    return _add;
    
    // //方法二,使用arguments.callee實(shí)現(xiàn)柯里化
    // return function () {
  //       if (arguments.length === 0) {
  //           return args.reduce(function(a,b){return a+b});
  //       }
  //       Array.prototype.push.apply(args, arguments);
  //       return arguments.callee;
  //   }
}
console.log(add(1,2,3)(1)(2)(3)(4,5,6)(7,8)());//42

實(shí)現(xiàn)的原理主要是:

  1. 閉包保存args變量,存儲(chǔ)之前的參數(shù)
  2. 新建一個(gè)_add函數(shù),參數(shù)的長度為0,就執(zhí)行加法,否則,存儲(chǔ)參數(shù)到args,然后返回函數(shù)自身(可以選擇匿名函數(shù),返回arguments.callee即可,意思相同,見代碼中注釋掉的部分,但是在嚴(yán)格模式下不能使用,所以還是使用方法一比較穩(wěn)妥)。

通用的函數(shù)來對(duì)普通函數(shù)進(jìn)行柯里化

可以看出來,柯里化其實(shí)是有特點(diǎn)的,需要一個(gè)閉包保存參數(shù),一個(gè)函數(shù)來進(jìn)行遞歸,這種模式是可以通過一個(gè)包裝函數(shù),對(duì)一些基本的函數(shù)進(jìn)行包裝之后的函數(shù)具有curry的特性。實(shí)現(xiàn)如下:

//  通用的函數(shù)柯里化構(gòu)造方法
function curry(func){
    //新建args保存參數(shù),注意,第一個(gè)參數(shù)應(yīng)該是要柯里化的函數(shù),所以args里面去掉第一個(gè)
    var args = [].slice.call(arguments,1);
    //新建_func函數(shù)作為返回值
    var _func =  function(){
        //參數(shù)長度為0,執(zhí)行func函數(shù),完成該函數(shù)的功能
        if(arguments.length === 0){
            return func.apply(this,args);
        }else {
            //否則,存儲(chǔ)參數(shù)到閉包中,返回本函數(shù)
            [].push.apply(args,arguments);
            return _func;
        }
    }
    return _func;
}

function add(){
    return [].reduce.call(arguments,function(a,b){return a+b});
}
console.log(curry(add,1,2,3)(1)(2)(3,4,5,5)(5,6,6,7,8,8)(1)(1)(1)());//69

上述代碼定義了一個(gè)add函數(shù),完成參數(shù)相加的功能,通過curry(add),使該函數(shù)能夠進(jìn)行柯里化,實(shí)現(xiàn)延遲執(zhí)行。也可以采用arguments.callee來實(shí)現(xiàn),原理相同,只是嚴(yán)格模式下不能使用而已。
事實(shí)上,我在看函數(shù)節(jié)流以及函數(shù)消抖的大神寫法的時(shí)候,其實(shí)應(yīng)該把函數(shù)執(zhí)行的上下文也保存下來,才能更加完善,有待后續(xù)更正。

柯里化的作用

看了很多文章,比較常見的功能有以下幾種:

  1. 事件綁定的時(shí)候檢測(cè)應(yīng)該用dom幾的方式,柯里化的話,只需要檢測(cè)一次,返回一個(gè)新的函數(shù)來進(jìn)行事件綁定(我覺得所有需要判斷的地方,如果判斷條件在一次訪問中不改變的話,都可以寫成柯里化的形式,從而達(dá)到只進(jìn)行一次判斷的效果)
  2. 利用柯里化的思想,可以自己寫一個(gè)bind函數(shù)
  3. 延遲執(zhí)行某個(gè)函數(shù),(在一些文章中看到回調(diào)函數(shù)可以借助這個(gè),還不是很理解,有待之后補(bǔ)充

小結(jié)

這篇文章主要是寫了一點(diǎn)自己學(xué)習(xí)函數(shù)柯里化之后的總結(jié),總體來說,學(xué)的時(shí)候很痛苦,學(xué)會(huì)了很簡單。可能知識(shí)就是這么神奇,會(huì)者不難。

參考文章

從一道面試題談?wù)労瘮?shù)柯里化(Currying)

簡單理解JavaScript中的柯里化和反柯里化

  1. 淺析 JavaScript 中的 函數(shù) currying 柯里化
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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