视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript函数式编程中compose实现
2020-11-27 20:24:36 责编:小采
文档

上一篇文章介绍了javascript函数式编程中curry(柯里化)的实现,当然那个柯里化是有限参数的柯里化,等有机会在补上无限参数的那一种柯里化,这次主要说的是javascript函数式编程中另外一个很重要的函数composecompose函数的作用就是组合函数的,将函数串联起来执行,将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。

简介

比如有这样的需求,要输入一个名字,这个名字有由firstName,lastName组合而成,然后把这个名字全部变成大写输出来,比如输入jacksmith我们就要打印出来,‘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.jsramda.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)
     }
    }

    这个就是思路,当然这样是不行的,没有退出条件,递归的退出条件就是最后一个函数执行完的时候,也就是count0的时候,这时候,有一点要注意,递归退出的时候,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的思路同上,不过是用迭代实现的,我就把它的源代码贴过来看一下

    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更加函数式,需要单独好好分析。

    下载本文
    显示全文
    专题