函数式编程

43 阅读3分钟
  • 发展历程:命令式 => 面向对象 => 函数式

1.由问题引发的思考

想把数组内的元素转换格式:'progressive$%coding' => { name: 'Progressive Coding' }

const _array = ['progressive$%coding', 'objective$%coding', 'functional$%coding']
const _objArr = [];
//[{ name: 'Progressive Coding' }, { name: 'Objective Coding' }, { name: 'Functional Coding' }]

    const nameParser = (array, objArr) => {
        array.forEach(item => {
            let names = item.split('$%') // split把字符串分割成字符串数组
            let newName = []
            // names: progressive, coding
            names.forEach(name => {
                // name[0]可以访问第一个字符
                let nameItem = name[0].toUpperCase() + name.slice(1)
                newName.push(nameItem)
            })
            _objArr.push({
                name: newName.join(' ')
            })
        })
        return objArr
    }
    nameParser(_array, _objArr)
  1. 得看完整段代码才能清楚具体要干什么

  2. 存在临时变量,并首尾封闭 -- 不易迭代拓展

    names.forEach(name => {
      let nameItem = name[0].toUpperCase() + name.slice(1);
      newName.push(nameItem)
    })
    这里定义的nameItem, 最后会被push到newName中应用下去,
    如果后面想修改,肯定要修改这里的代码
    

2.解决方案 -- 函数化

  1. 需求分析--数组 => 数组对象
  • nameParser => 生成一个数组对象 [ objhelper ]
  • objhelper => 组装对象内容{ string: string}
  1. 功能明确
  • objHelper需要key,value => formateName(value值格式化) + assembleobj(组装成obj)
  1. 功能拆分 原子函数
  • formateName => split(字符串拆分) + capitalize(首字母大写) + join(组合)
  • assembleobj => 拆不了了
  1. 代码实现

原子操作

    const assembleObj = (key, value) =>{
     let obj = {};
     obj[key] = value;
    return obj;
    }
    const capitalize = (str) => str[0].toUpperCase()+str.slice[1];

组装方案

const formateName = 组装合并(join(),map(capitlize),split('$%'))
const objHelper = 组装合并(assembleObj('name'), formateName)
const nameParser = map(objHelper)

函数式编程

1.原理

  • 加法结合律
  • 将耦合的业务逻辑拆成原子函数
  • 原子函数通过拼装可以实现之前的功能
  • 并且传参和结果保持不变,和以前一样

2.理论思想

转化思维,日常生活中通常是由概括到具体去实现一件事情;

函数式编程则需要先确定每一个最小的点,然后合成事情

a.函数是一等公民
  1. 函数 -- 逻辑功能实现的落脚点
  2. 实现原子函数 + 拼接流程
b.声明需求的习惯进行

例如想要实现什么功能,1,2,3...

reacthook就是原子函数,它本身不知道我们要实现的功能,而基于对这些原子函数的使用达到我们想要的效果

c.传参统一成一个(通常上一个函数结果传给下一个函数)

=> 返回一个:惰性函数

=> 传入一个:柯里化

d.纯函数

3.惰性函数

4.纯函数 - 无状态&无副作用

  • 无状态 - 多次执行,实现的功能是一样的;不改变传入的参数

  • 无副作用 - 函数内部执行,对系统外部 没有影响

    const class = {name: 'aaa'}
    
    const fun1 = str => class.name = class.name + str;
    // 违反无副作用,改变了系统内的变量
    
    const fun2 = obj => obj.name += '1';
    //违反无状态,修改了传入的参数
    
    const pufun = name => name += '1';
    //纯函数  pufun(class.name)
    

5.流水线组装 - 加工 组装

a.加工,传参处理 - 柯里化

f(x,y,z) => f(x)(y)(z)

  const sum = (x, y) => { return x + y }
  sum(1, 2);
  
  const sun = (x) => {
      return (y) => {
         return x + y
      }
  }
  sum(1)(2)
  • 手写构造可拆分的传参累加函数 add(1)(2)(3)(4)...
  1. 构造柯里化结构,多次传参构成传入的参数

  2. 外层arguments(类数组处理)

  3. 传入参数无限拓展(递归) => 返回递归函数本身

  4. 主功能 - 累加

  5. 组装输出

    const add = function(){
      // arguments默认为函数调用时 传入的参数
      let args = Array.prototype.slice.call(arguments);
      
      //组装传参
      let inner = function(){
          args.push(...arguments)
          return inner
      }
      //计算值
      inner.tostring = function(){
         return args.reduce((prev, cur) => {
           return prev + cur;
         })
      }
    
    //输出是函数/又能获取值 
    return inner
    }
    

add(1)(2)(3)

  • add(1)返回的是inner函数;
  • add(2)调用的是inner,把参数累加到args中
  • add(3)同上
  • 当要获取值时,就直接 +add(1)(2)(3)
b.组装 -- 流水线
const compose = (f, g) => x => f(g(x))

const sum1 = x => x + 1
const sum2 = x => x + 2
const sum12 = compose(sum1, sum2)
sum12(1)

具体实现

 // 命令式
trim(reverse(toUpperCase(map(arr))))

// 对象式
arr.map().toUpperCase().reverse().trim()

// 函数式
compose(trim, reverse, toUpperCase, map)
pipe()

BOX和函子 functor,高阶对象

// 一封信
 class Mail {
    constructor(content) {
        this.content = content
    }
    map(fn) { // 接受一个函数,并返回一个新对象
        return new mail(fn(this.content))
    }
}

// 1. 拆开信
let mail1 = new Mail('love')

// 2. 读信
let mail2 = mail1.map(function(mail) {
    return read(mail)
})
// 3. 烧
let mail3 = mail1.map(function(mail) {
    return burn(mail)
})
// 4. 老妈查看
mail3.map(function(mail) {
    return momCheck(mail)
})

// 链式
new Mail('love').map(read).map(burn).map(momCheck)