天天看點

JavaScript 函數式程式設計中 compose 實作

簡介

比如有這樣的需求,要輸入一個名字,這個名字有由firstName,lastName組合而成,然後把這個名字全部變成大寫輸出來,比如輸入jack,smith我們就要列印出來,‘HELLO,JACK SMITH’ 。

我們考慮用函數組合的方法來解決這個問題,需要兩個函數greeting, toUpper

var greeting = (firstName, lastName) => 'hello, ' + firstName + ' ' + lastName
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack', 'smith'))
// ‘HELLO,JACK SMITH’      

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

  • compose的參數是函數,傳回的也是一個函數
  • 因為除了第一個函數的接受參數,其他函數的接受參數都是上一個函數的傳回值,是以初始函數的參數是多元的,而其他函數的接受值是一進制的
  • compsoe函數可以接受任意的參數,所有的參數都是函數,且執行方向是自右向左的,初始函數一定放到參數的最右面

知道這三點後,就很容易的分析出上個例子的執行過程了,執行fn('jack', 'smith')的時候,初始函數為greeting,執行結果作為參數傳遞給toUpper,再執行toUpper,得出最後的結果,compose的好處我簡單提一下,如果還想再加一個處理函數,不需要修改fn,隻需要在執行一個compose,比如我們再想加一個trim,隻需要這樣做

var trim = str => str.trim()
var newFn = compose(trim, fn)
console.log(newFn('jack', 'smith'))      

就可以了,可以看出不論維護和擴充都十分的友善。

實作

例子分析完了,本着究其根本的原則,還是要探究與一下compose到底是如何實作的,首先解釋介紹一下我是如何實作的,然後再探求一下,JavaScript函數式程式設計的兩大類庫,lodash.js和ramda.js是如何實作的,其中ramda.js實作的過程非常函數式。

我的實作

我的思路是,既然函數像多米諾骨牌式的執行,我首先就想到了遞歸,下面就一步一步的實作這個compose,首先,compose傳回一個函數,為了記錄遞歸的執行情況,還要記錄參數的長度len,還要給傳回的函數添加一個名字f1。

var compose = function(...args) {
    var len = args.length
    return function f1() {

    }
}      

函數體裡面要做的事情就是不斷的執行args中的函數,将上一個函數的執行結果作為下一個執行函數的輸入參數,需要一個遊标count來記錄args函數清單的執行情況。

var compose = function(...args) {
    var len = args.length
    var count = len - 1
    var result
    return function f1(...args1) {
        result = args[count].apply(this, args1)
        count--
        return f1.call(null, result)
    }
}      

這個就是思路,當然這樣是不行的,沒有退出條件,遞歸的退出條件就是最後一個函數執行完的時候,也就是count為0的時候,這時候,有一點要注意,遞歸退出的時候,count遊标一定要回歸初始狀态,最後補充一下代碼

var compose = function(...args) {
        var len = args.length
        var count = len - 1
        var result
        return function f1(...args1) {
            result = args[count].apply(this, args1)
            if (count <= 0) {
                count = len - 1
                return result
            } else {
                count--
                return f1.call(null, result)
            }
        }
    }      

lodash實作

var flow = function(funcs) {
    var length = funcs.length
    var index = length
    while (index--) {
        if (typeof funcs[index] !== 'function') {
            throw new TypeError('Expected a function');
        }
    }
    return function(...args) {
        var index = 0
        var result = length ? funcs[index].apply(this, args) : args[0]
        while (++index < length) {
            result = funcs[index].call(this, result)
        }
        return result
    }
}
var flowRight = function(funcs) {
    return flow(funcs.reverse())
}