上一篇文章介绍了 javascript
函数式编程中 curry
(柯里化)的实现,当然那个柯里化是有限参数的柯里化,等有机会在补上无限参数的那一种柯里化,这次主要说的是 javascript
函数式编程中另外一个很重要的函数 compose
, 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
的思路同上,不过是用迭代实现的,我就把它的源代码贴过来看一下
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
更加函数式,需要单独好好分析。