JS函數式編程中compose的實現

有以下需求:
創建一個compose函數,返回函數集 functions 組合后的復合函數, 也就是一個函數執行完之后把返回的結果再作為參數賦給下一個函數來執行. 以此類推. 在數學里, 把函數 f(), g(), 和 h() 組合起來可以得到復合函數 f(g(h()))。

如果只是為了完成這道題,可用以下做法

var greet = function(name){
  return 'hi:'+name
}
var exclaim = function(statement){
  return statement.toUpperCase()+'!' 
} 
var compose = function(greet,exclaim){
  return function(name){
    console.log(exclaim(greet(name)).replace(/(\w+:)/,function($1){
      return $1.toLowerCase() 
    }))  
  }  
} 

var welcome=compose(greet,exclaim)
welcome('dot')

//'hi: DOT!'

但上面的代碼沒有擴展性,如果嵌套的函數更多,該怎么解決呢?
我們可以定義兩個方法,分別是compose()和pipe()(上面的題目我們用compose就可以實現,pipe是另行擴展的,不要有疑問),這兩個方法接收的參數都是N個函數,返回的值都是一個函數。

  • compose內的函數執行順序為從右向左,即最右邊的函數(最后一個參數)最先執行,執行完的結果作為參數傳遞給前一個函數(包裹它的函數),一直到整個函數執行完畢,return一個函數,所以compose內部實現的原理類似多米諾骨牌,層層遞進的。
  • pipe函數與compose函數十分相近,也是一個函數執行完畢后將結果作為參數傳遞給另一個函數,但它們的區別僅在于pipe函數的接收的函數參數,是從左向右執行的,即第一個參數(函數)執行完畢,將結果吐出來作為參數傳遞給第二個函數,也就是pipe的第二個參數,直到pipe所有參數作為函數都執行完畢,return出一個函數,才算執行完成。

compose和pipe的優點在于,哪怕再要增加或者刪除一個參數(執行函數),只需增加或刪除相應的參數和定義的函數即可,維護和擴展都十分方便。

代碼實現如下:

function compose() {
    var fns = [].slice.call(arguments)
    return function (initialArg) {
        var res = initialArg
        for (var i = fns.length - 1; i > -1; i--) {
            res = fns[i](res)
        }
        return res
    }
}

function pipe() {
    var fns = [].slice.call(arguments)
    return function (initialAgr) {
        var res = initialAgr
        for (var i = 0; i < fns.length; i++) {
            res = fns[i](res)
        }
        return res
    }
}

var greet = function (name) { return 'hi:' + name }
var exclaim = function (statement) { return statement.toUpperCase() + '!' }
var transform = function (str) { return str.replace(/[dD]/, 'DDDDD') }
var welcome1 = compose(greet, exclaim, transform)
var welcome2 = pipe(greet, exclaim, transform)
console.log(welcome1('dot'))//hi:DDDDDOT!
console.log(welcome2('dolb'))//HI:DDDDDOLB!

根據前面說過的原理,分析一下以上代碼:

出題人的意圖是“把函數 f(), g(), 和 h() 組合起來得到復合函數 f(g(h()))”,我們首先就可以想到遞歸,函數從右至左依次執行,每執行完一個函數將其結果作為參數傳遞給它左邊的函數(即包裹它的函數),我們可以很清楚地知道,compose接收的是N個函數參數,而其中每個參數作為函數在執行時接收的都是一個參數(前一個被執行的函數的結果)。

只看代碼就可以發現,調用compose函數,將得到的結果賦值給welcome1變量,但這時我們并沒有直接把welcome1打印出來,而是向welcome1里傳入了參數,這就很像函數調用的格式有木有,那么我們可以做一個設想,其實compose返回的就是一個匿名函數,我們可以通過傳遞參數給這個匿名函數來得到某種結果,現在思路就很清晰了。

首先在頁面上定義一個compose函數,但是不傳遞任何參數,因為參數的數量(即執行函數的個數)是不確定的,在compose函數內部return一個匿名函數,這個匿名函數接收一個形參initialAgr,函數執行過程中這個參數就是傳遞給welcome1的實參,也就是第一個被執行的函數接收的參數。
到這里完成了以下代碼:

function compose() {
    return function (initialArg) {

    }
}

接下來干什么呢?
看到f(g(h()))這種形式的函數嵌套,我首先想到的就是遞歸,所以我們首先要取得調用compose函數時傳遞的參數,這個參數的形式如下:

arguments = {
    0: fn1,
    1: fn2,
    2: fn3,
    length: 3
}

為了實現遞歸,我們需要把這個形式的參數轉換為數組,借用數組的slice方法可以實現這一點:

var fns = [].slice.call(arguments)

以上代碼中,我們將傳遞給compose函數的參數轉化為了一個數組并賦值給了fns,接下來我們用一個變量res將傳遞給welcome1的參數保存起來,定義一個for循環從右向左遍歷fns中的每一項并執行它,在這段代碼中函數的執行順序是transform=>exclaim=>greet,其中,將res傳遞給第一個被執行的函數,并將值賦值給res,相當于每執行完一個函數,res的值就被重寫一次。
for循環結束意味著復合函數已經執行完畢,我們要的無非就是res的值,所以在函數內部return res就可以達到我們預期的效果。

綜上所述,compose函數定義如下:

function compose() {
    var fns = [].slice.call(arguments)
    return function (initialArg) {
        var res = initialArg
        for (var i = fns.length - 1; i > -1; i--) {
            res = fns[i](res)
        }
        return res
    }
}

這就是compose大致的使用,總結下來要注意的有以下幾點:

  • compose的參數是函數,返回的也是一個函數。
  • 因為除了第一個函數的接受參數,其他函數的接受參數都是上一個函數的返回值,所以初始函數的參數是多元的(本題只討論了一元參數的情況),而其他函數的接受值是一元的。
  • compsoe函數可以接受任意的參數,所有的參數都是函數,且執行方向是自右向左的,初始函數一定放到參數的最右面。

pipe函數沒有什么好說的,與compose的原理相同,只不過函數的執行順序是從左至右,體現在以上代碼中就是greet=>exclaim=>transform

可以看出,compose和pipe函數對相同執行函數的執行順序不同,得到的結果也不同。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容