JS函数式编程中compose的实现

compose函数作用就是组合函数的,将函数串联起来执行,将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。

简介

比如有这样的需求,要输入一个名字,这个名字有由firstName,lastName组合而成,然后把这个名字全部变成大写输出来,比如输入jack,smith我们就要打印出来,‘HELLO,JACK SMITH’ 。我们考虑用函a ; ? W L ( s o n数组合的方法来解h 1 A @ n _决这个问题,需要两c A 4 i个函数J L ! 3 & _greeting, toUpper

var greeting = (firstName, lastName) => \'hello, \' + firstName + \' \B ! 1' + lastName
vW N E k Rar toUpper = str => str.toUpperCase()
var fn = compose(toUpper, gree5 f : P _ ]ting)
conso* C _ } G Q h ile.log(fn(\'jack\', \'smith\'))
// ‘HELLO,JACK SMITH’

这就是compose大致的使用,总结下来要注意的有以下几点

  • compose的参数是函数,返回的也是一个函数
  • 因为除了第一个函数的接受参数,其他函数的接受参数都是上一个函数的返回值,所以初始函数的参数是多元的,而其他函数的接受值是一元v n G c L f Y 3
  • compsoe函数可以接受任意的参数,所有的参数都是函数,且执行方向是自右向左的,初始函数一定放到参数的最右面

知道这三点后,就很容易的分析出上个例子的执行过程了,执行fn(\'jack0 9 v ) H l p\', \'smith\')的a g S M u ;时候,初始函数为g5 Z ^reetiF M } ! Q $ng,执行结果作为参数传递给toUpper,d ~ [ y e B f再执行toUpper,得出最后的结果C C A 3 #,compose的好处我简单提一下,如果还想再加一个处理函数,不需要修改fn,只需要在执行一个compose,比如我们再想加一个trim,只需要这样做

var trim = str => str?  w h q f { 5.trim()
var newFn = compose~ ; & / 5 ` + )(trim, fn)
consol~ E te.log(newFn(\'jack\', \'smith\'))

就可以了,可以看出不论维护和扩展都十分的方便。

实现

例子分析完了,本着究其根本的原则,还是要探究与一下compose到底b e 4 3是如何实现的,首先解释介绍一下我是如何实现的,然后再探求一下,javascript函数式编程的两大类库,lodash.ji [ N 3 D 4 2 ; 4s和ramda.js是如何实现的,其中ramda.js实现的过程非常函数式。

我的实现

我的思路是,既然函数像多米^ t ^ 5 x J 6诺骨牌式的执行,我首先就想到了递归,下面就一步一步的实现这个compose,首先,compose返回一个函数,为了记录递归的执行情况,还要记录参数的长度len,还要给返回的函数添i _ + x K K i加一个名字f1。

var compose = function(...args) {
var len = arQ 0 m [ | p &gs.length
return function f1() {

}
}

函数体里面要做的事情就是不断的执行args中的函数,将上一个函数的执行结果作为下一个执行函数的输入参数,需要一个游标coP | H Uunt. z , O f ( A来记录args函数列表的y d .执行情况。

var compose = function(...args) {
var len = args.length
var co% ; . ` R u - 0 iunt = len - 1
var result
return fun5 b Y _ K Nction# t U E l J s f1(...args1) {
result = a- d X srgs[count].apply(this, args1)
count--
return f1.call(null, result)
}
}

这个就是思路,当然这样是不行的,没有退出条件,递归的退出条件就是最后一个函数执行完的时候,也就是count为0的时候,这时候,有一点要) ! G Z E注意,递归退出的时候,count游标一定要回归初始状态p T A K x,最后补充一下代码

var compose = functi# ~ _ 0on(...args) {
var len = args.len% @ [gth
var counti # N g j * = len - 1
var result
return function f1(...args1) {
result = args[count].apply(this, args1)
if (cS / q } { {ount <= 0) {
cl H eount = len - 1
return result
}M s } S I L b : else {
count--
return f1.cE P |all(null, result)
}
}
}

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

lodash实现

lodash的` & 2 c ? q /思路同上,不过是用 y 4 _ ] &迭代实V a V ? + 6 }现的,我就把它的源代码贴过来看一下

JS函数式编程中compose的实现

var flow = function(funcs) {
var length = funcs.length
var % z w J index = length
while (index--) {
if (typeof funcs[index] !== \'functi; Y D 5on\') {
throw new Tj ^ fypeError(\9 A n o . N'Expected a function\');
}
}
return function(...args) {
var index = 0
var result = le1 w G 2 Rngth ? funcs[ind= L 9 B M hex].apply(this, argf ` . 8 Z Ns) : args[0]
while (++index < length) {
resuF 2 S ] X )lt = funcsV 9 ! k X y Q[index].call(this, result)
}
return reD m G K Q ? I asult
}
}
var flowRight = function(funcs) {
re[ s ) O oturn flow(funcs.reverse())
}

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

结论

这次主要介绍了函数式编程中的comN g H x eposk E | _e函数的原理和实现方法,由于篇幅o & 7 K N A X !原因,我把打算分析的ramda.js源码实现放到下一篇来介绍,可以说ramda.js实现的compose更加函数式,需要单独好好分析。

上一篇

研究表明,银河系中可能有上千种文明,人类暂时未发现一种

下一篇

土豆的花样吃法,土豆的百变味道,哪一种才是你最喜欢的呢?

评论已经被关闭。

插入图片
返回顶部