[前端进阶] 由浅入深学会函数组合,进阶必备技能!

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与 「掘力星计划」      ,赢取创作大礼包,挑战创作激励金

前言

在我们进入学习函数组合之前,先让我们回忆一下柯里化和纯函数

在我们回忆完毕这两个概念之后,让我们来看个问题:

纯函数和柯里化很容易写出洋葱代码 h(g(f(x)))

啥是洋葱代码

u=2436579106,2121449035&fm=26&fmt=auto.webp 像洋葱的切面一样一层一层的嵌套在一起的代码 '(当然还有面条式代码,这种代码才是最垃圾的,千万避免)'

让我们来一个实际一点的案例

_.toUpper(_.first(_.reverse(array)))
复制代码

像这样的代码一层一层传递,一层包裹一层就是洋葱代码,使用函数组合就可以避免这种情况

管道

在我们深入了解函数组合的时候,我们先看看以下的内容:

什么是管道

下面这张图表示程序中使用函数处理数据,a参数输入给函数fn,在fn中经过各种逻辑处理从而得出结果b,这就是数据a通过管道fn之后变成了bimage.png

管道拆分

上图中的管道看起来很方便,因为我们只需要维护这个管道就好

Q: 管道的长度、广度过大的时候,如果这个管道出现了一些纰漏,那我们排查起来的工作量就很大,很困难,该如何处理呢?

A: 拆分管道!

下面这张图我们将管道fn拆分成了f3f2f1,再通过参数a的输入之后,经过f3管道的处理获得mm在通过f2,如此进行,最终获得结果b

经过这样的拆分,我们可以保证每个函数的颗粒度,在之后出现问题的时候,我们可以更快更精准的进行问题打击,一击致命!bug来的快,去的更快!(ps:中间产生的中间结果我们是不需要进行考虑的) image.png 我们使用伪代码描述一下这张图

fn = compose(f1,f2,f3)
b = fn(a)
复制代码

函数组合(compose)

什么是函数组合

函数可以让我们把细粒度的函数重新组合生成一个新的函数

  • 函数就像是数据管道,而函数组合就是把这些进行连接起来,让数据穿过多个管道形成最终结果
  • 函数组合默认是从右到左执行

lodash中的组合函数

flow

创建一个函数。 返回的结果是调用提供函数的结果,this 会绑定到创建函数。 每一个连续调用,传入的参数都是前一个函数返回的结果。

参数

  1. [funcs] (...(Function|Function[])) : 要调用的函数。

返回

(Function) ( 返回新的函数。) 例子

// 引入lodash
const _ = require('lodash')

function square(n) {
  return n * n;
}
function add(augend,augend) {
    return augend + augend;
} 
const addSquare = _.flow([add, square]);
// 当然也可以这样使用
// const addSquare = _.flow(add, square);
addSquare(1, 2); // => 9
复制代码

flowRight

这个方法类似_.flow,除了它调用函数的顺序是从右往左的。

参数

  1. [funcs] (...(Function|Function[])) : 要调用的函数。

返回

(Function) ( 返回新的函数。)

例子

// 引入lodash
const _ = require('lodash')

function square(n) {
  return n * n;
}
function add(augend,augend1) {
    return augend + augend1;
} 
const addSquare = _.flowRight([square,add]) ;
// 当然也可以这样使用
// const addSquare = _.flowRight(square,add);
addSquare(1, 2); // => 9
复制代码

手写函数组合demo

老话说得好,磨刀不误砍柴工,在我们下手之前,我们先依据lodash库中的两个函数做一下需求的整理吧!

  • 函数可以接收参数,如果是数组的话,有且只能有一个,如果不为数组的话参数的数量不限
  • 函数需要返回一个函数,且我们返回的函数要对数据进行处理

ok带着这些需求我们先整理一份简单的demo出来吧

1.获取参数

// 老规矩es6的剩余参数
function flow(...funs) {
    console.log(funs)
}
复制代码

ok,我们第一步已经完成了,不过有几个问题,我们如果传入flow(方法一,方法2) 打印出来的结果会是[方法一,方法2],但是我们传入flow([方法一,方法2])打印出来的结果会是[[方法一,方法2]],这样对我们的之后进行不是很方便,我们稍微做一下数组的扁平化吧!

2.数组扁平化

遍历数组每一项,若值为数组则递归遍历,否则concat。

function flatten(arr) {  
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}
复制代码

但是我们要进行一下改造,刚刚我们的扁平化方法其实是进行递归扁平化的,但是我们不需要这样,这样就和我们传递的参数格式不一样了,改造改造,这样就好

function flatten(arr) {  
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? [...item] : item);
    }, []);
}
复制代码

3.第三步 完结版本

既然我们的参数已经做了扁平化操作,那我们就简单了,遍历一次扁平化后的数组,进行一次判断,是否每个参数都是方法不是的话,就给他抛出错误

// 老规矩es6的剩余参数
function flow(...funs) {
  // 这里我们要进行一下判断中是否都是方法体
  let funList = flatten(funs)
  const length = funList.length
  for(const fun of funList){
     if (typeof fun !== 'function') {
      throw new TypeError('Expected a function')
    }
  }
  return function(...args) {
    let index = 0
    let result = length ? funList[index].apply(this, args) : args[0]
    while (++index < length) {
      result = funList[index].call(this, result)
    }
    return result
  }
}
复制代码

测试

const addSquare1 = flow(add,square) // 预计9
console.log('直接传入函数:',addSquare1(1,2)) 

const addSquare2 = flow([add,square]) // 预计9
console.log('传入数组:',addSquare2(1,2)) // 预计结果:9
复制代码

image.png

小小的优化

可是flowRight的怎么办?

再写一遍吗?不行太麻烦了,我们进行一下封装吧!

function create(isRight){
    return function (...funs) {
      let funList = flatten(funs)
      if(isRight){
          funList.reverse();
      }
      // 这里我们要进行一下判断中是否都是方法体
      
      const length = funList.length
      for(const fun of funList){
         if (typeof fun !== 'function') {
          throw new TypeError('Expected a function')
        }
      }
      return function(...args) {
        let index = 0
        let result = length ? funList[index].apply(this, args) : args[0]
        while (++index < length) {
          result = funList[index].call(this, result)
        }
        return result
      }
    }
}
复制代码

这样我们使用 flow的时候这样运行就可以了const flow = create(),flowRight的时候这样运行就可以了 const flowRight = create(true)

测试一下

const flow = create()
const flowRight = create(true)

const addSquare1 = flow(add,square) // 预计9
console.log('flow直接传入函数:',addSquare1(1,2)) 

const addSquare2 = flow([add,square]) // 预计9
console.log('flow传入数组:',addSquare2(1,2)) // 预计结果:9

const addSquare3 = flowRight(square,add) // 预计9
console.log('flowRight直接传入函数:',addSquare3(1,2)) 

const addSquare4 = flowRight([square,add]) // 预计9
console.log('flowRight传入数组:',addSquare4(1,2)) // 预计结果:9
复制代码

image.png

完整代码

function square(n) {
  return n * n;
}
function add(augend,augend1) {
    return augend + augend1;
} 
function flatten(arr) {  
  return arr.reduce((result, item)=> {
      return result.concat(Array.isArray(item) ? [...item] : item);
  }, []);
}
function create(isRight){
  return function (...funs) {
    let funList = flatten(funs)
    if(isRight){
      funList.reverse();
    }
    // 这里我们要进行一下判断中是否都是方法体
    const length = funList.length
    for(const fun of funList){
       if (typeof fun !== 'function') {
        throw new TypeError('Expected a function')
      }
    }
    return function(...args) {
      let index = 0
      let result = length ? funList[index].apply(this, args) : args[0]
      while (++index < length) {
        result = funList[index].call(this, result)
      }
      return result
    }
  }
}
const flow = create()
const flowRight = create(true)


const addSquare1 = flow(add,square) // 预计9
console.log('flow直接传入函数:',addSquare1(1,2)) 

const addSquare2 = flow([add,square]) // 预计9
console.log('flow传入数组:',addSquare2(1,2)) // 预计结果:9

const addSquare3 = flowRight(square,add) // 预计9
console.log('flowRight直接传入函数:',addSquare3(1,2)) 

const addSquare4 = flowRight([square,add]) // 预计9
console.log('flowRight传入数组:',addSquare4(1,2)) // 预计结果:9
复制代码

小结

到这里我们简单版本的组合函数已经解决了,但是还是有点问题的,思路没问题,希望看到的这里的同学给个赞吧!

可以的话也拿去试试看,一起发现问题一起进步!

求赞.jpeg

猜你喜欢

转载自juejin.im/post/7017420295376109576