• 欢迎访问新概念博客,研究javascript,css3,html5,nodejs,Ext js等技术研究,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站,欢迎加入新概念博客

JS函数式编程中compose的实现

经典语录 新概念 4年前 (2020-11-30) 1919次浏览 0个评论 扫描二维码

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)
            }
        }
    }

 

这样就实现了这个 compose 函数。后来我发现递归这个完全可以使用迭代来实现,使用 while 函数看起来更容易明白,其实 lodash.js 就是这么实现的。

lodash 实现

lodash 的思路同上,不过是用迭代实现的,我就把它的源代码贴过来看一下

JS函数式编程中compose的实现

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())
}

 

可以看出,lodash 的本来实现是从左到右的,但也提供了从右到左的 flowRight,还多了一层函数的校验,而且接收的是数组,不是参数序列,而且从这行 var result = length ? funcs[index].apply(this, args) : args[0]可以看出允许数组为空,可以看出还是非常严谨的。我写的就缺少这种严谨的异常处理。

结论

这次主要介绍了函数式编程中的 compose 函数的原理和实现方法,由于篇幅原因,我把打算分析的 ramda.js 源码实现放到下一篇来介绍,可以说 ramda.js 实现的 compose 更加函数式,需要单独好好分析。


新概念博客 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:JS函数式编程中compose的实现
喜欢 (2)
[新概念]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址