Skip to content

reduce实现compose和中间件

函数的深度嵌套调用

通常当下一个调用的函数需要使用到上一个函数执行结果的时候,我们一般会这么做:

例如 吃一次饭:

javascript
// 买食物
function buyFood(moeny) {
    return 'original food'
}
// 处理食材
function dealFood(originalFood) {
    return 'ingredients'
}
// 烹饪
function cookFood(ingredients) {
    return 'food'
}
// 吃
function eat(food){}

我们大约需要经历这些步骤,那么一次午饭我们这样:eat(cookFood(dealFood(buyFood(moeny)))),吃一次晚饭:eat(cookFood(dealFood(buyFood(moeny))))...

这种方式不仅增大代码量,更增加阅读和维护的难度;如果业务突然有了改动:一家人吃饭,你怎么能做好了自己就开吃,你得喊家人一起吃对吧?

那么烹饪和吃之间我们需要加一个:

javascript
// 喊家人吃饭
function callFamily(ingredients){
    return 'Family'
}

更好得方式肯定是整合封装一下:

javascript
function eatMeal(money){
    return eat(cookFood(dealFood(buyFood(moeny))))
}

这样调用的时候,也只需要eatMeal(money),需要callFamily也能直接修改eatMeal即可。但是这样,如果更多的嵌套,就有更多的层叠关系。而我们希望通过类似这样的关系来描述:step1 => step2 => step3 ..

通过reduce实现compose

javascript
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

我们需要的结果类似这样:

javascript
[step1,step2,step3,step4 ... ].reduce(function(arg,fn){
    return fn(arg)
},arg)

传入一个参数arg,顺序执行数组中的函数,并且后续每一次的入参都是上一步的结果,执行完毕后返回结果。

封装一下:

javascript
function compose(fns, arg) {
    Array.prototype.reduce.call(fns, (pre, cur) => {
        return cur(pre)
    }, arg)
}
compose([step1,step2,step3],arg)

但是和第一次问题一样,每一次调用都必须带上所有步骤,不利于编码和维护。

我们需要的是一次定义,多次调用时候只传入参数:

javascript
function compose() {
    var fns = arguments;
    return function (arg) {
        return Array.prototype.reduce.call(fns, (pre, cur) => {
            return cur(pre)
        }, arg)
    }
}
function add(num) {
    var result = num + 1;
    console.log(result)
    return result;
}
var add3Times = compose(add, add, add)
console.log(add3Times(2))

再对比网上流传的compose:

javascript
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

多了两个判断:判断没有传入的函数和传入一个函数的时候处理,并且和上面的实现不同,reduce每一步返回的并不是上一步的结果,而是一个函数,最后一样返回一个函数。

第一种情况返回的函数中,函数体内包括reduce的执行,所以后续每次调用都需要重新reduce然后返回结果,并且因为占用arguments,所以compose的执行栈也会一直存在内存中;而第二种返回的是已经经过reduce组装之后的函数,不再占用,所以原来的执行栈可以释放。

修改一下:

javascript
function compose() {
    var fns = arguments;
    return Array.prototype.reduce.call(fns, (pre, cur) => {
        return (arg) => {
            return cur(pre(arg))
        }
    })
}

中间件

中间件就是一个函数,可以通过中间件组合成一个最终你想要的逻辑函数。