JavaScipt【函数式编程】

定义:用来描述数据(函数)之间的映射。(可以理解为对运算过程的抽象)

学习意义:

  • 函数式编程是随着 React 的流行受到越来越多的关注
  • Vue 3也开始拥抱函数式编程
  • 函数式编程可以抛弃 this
  • 打包过程中可以更好的利用 tree shaking 过滤无用代码
  • 方便测试、方便并行处理
  • 有很多库可以帮助我们进行函数式开发:lodash、underscore、ramda
// 非函数式 
let num1 = 2 
let num2 = 3 
let sum = num1 + num2 

// 函数式 
function add (n1, n2) {
    
    
    return n1 + n2 
}
let sum = add(2, 3)

前置知识

  • 函数是一等公民
  • 高阶函数
  • 闭包

函数是一等公民(First-class Function):

  1. 函数可以存储在变量中(声明式函数);
  2. 函数可以作为参数;
  3. 函数可以作为返回值;
// 函数的方法赋值给另一个方法 
const BlogController = {
    
    
  index(posts){
    
    
    return Views.index(posts)
  }
};
// 等同于
const BlogController = {
    
    
  index:Views.index
};
 

高阶函数(Higher-order Function):

  • 可以把函数作为参数传递给另一个函数
  • 可以把函数作为另一个函数的返回结果
    • 函数作为参数:
// 函数可以作为参数:下面自定义了一个forEach函数,fn作为参数,所以自定义函数forEach为高阶函数
function forEach(array,fn){
    
    
  for(let value of array){
    
    
    fn(value);
  }
}
let arr = [1,2,3,5,8]
forEach(arr,item => {
    
    
  console.log(item)
})
    • 函数作为返回值:
//函数可以作为返回值:因为makeFn返回的是一个函数,调用时也可以使用makeFn()()
function makeFn(){
    
    
  let msg = "hello world!"
  return function(){
    
    
    console.log(msg);
  }
}
let fn1 = makeFn()
fn1()
// 综合案例:只执行一次的函数
function once(fn){
    
    
  let done = false;
  return function(){
    
    
    if(!done){
    
    
      done = true;
      fn.apply(this,arguments)
    }  
  }
}
let pay = once(function(money){
    
    
  console.log('支付了' + money + '元');
})
pay(1888)
pay(2000)//不会执行

常用高阶函数

  • forEach
  • map
  • fifilter
  • every
  • some
  • fifind/fifindIndex
  • reduce
  • sort
  • ……

闭包:

定义:可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
// makeFn内部作用范围延长
function makeFn () {
    
     
    let msg = 'Hello function' 
    return function () {
    
     
        console.log(msg) 
    } 
}
const fn = makeFn() 
fn()

  • 闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是

    堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员

  • 闭包案例:

// 求函数幂的调用:内部函数访问了外部函数的power参数,所以外部函数不会被释放
function makePower(power){
    
    
  return function(number) {
    
    
    return Math.pow(number,power)
  }
}
let pow2 = makePower(2)
let pow3 = makePower(3)
console.log(pow2(2))//打印4
console.log(pow3(3))//打印27

// 求员工工资
function makeSalary(salary) {
    
    
  return function(performance) {
    
    
    return salary + performance
  }
}
let sal2 = makeSalary(20000)
let sal3 = makeSalary(30000)
console.log(sal2(2000))//打印22000
console.log(sal3(3000))//打印33000

纯函数

定义:相同的输入永远会得到相同的输出**,而且没有任何可观察的副作用
  • 例:数组的 slice 和 splice 分别是:纯函数和不纯的函数
  • slice 返回数组中的指定部分,不会改变原数组
  • splice 对数组进行操作返回该数组,会改变原数组

柯里化 (Haskell Brooks Curry)

  • 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变),然后返回一个新的函数接收剩余的参数,返回结果
  • 总结

    • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数

    • 这是一种对函数参数的’缓存’

    • 让函数变的更灵活,让函数的粒度更小

    • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能

函数组合

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

    • 获取数组的最后一个元素再转换成大写字母, .toUpper(.first(_.reverse(array)))
  • 函数组合可以让我们把细粒度的函数重新组合生成一个新的函数

  • 函数组合 (compose):如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间

    过程的函数合并成一个函数

    • 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
    • 函数组合默认是从右到左执行
  • lodash中的组合函数:组合函数 flow() 或者 flowRight(),他们都可以组合多个函数( flflowRight() 是从右到左运行,使用的更多一些)

  • 函数的组合要满足结合律 (associativity):

    • 我们既可以把 g 和 h 组合,还可以把 f 和 g 组合,结果都是一样的
    // 结合律(associativity) 
    let f = compose(f, g, h) 
    let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) // true
    

函子(Functor

什么是 Functor

  • 容器:包含值和值的变形关系(这个变形关系就是函数)
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运行一个函数对值进行处理(变形关系)

Functor 函子

class Container {
    
     
    // of 静态方法,可以省略 new 关键字创建对象 
    static of (value) {
    
     
        return new Container(value) 
    }
    constructor (value) {
    
     
        this._value = value 
    }
    // map 方法,传入变形关系,将容器里的每一个值映射到另一个容器 
    map (fn) {
    
     
        return Container.of(fn(this._value)) 
    } 
}
// 测试 
Container.of(3) 
    .map(x => x + 2) 
    .map(x => x * x)
  • 总结:
    • 函数式编程的运算不直接操作值,而是由函子完成
    • 函子就是一个实现了 map 契约的对象
    • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
    • 想要处理盒子中的值,我们需要给盒子的 map 方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
    • 最终 map 方法返回一个包含新值的盒子(函子)
常用函子:
  • Maybe函子 :可以对外部的空值情况做处理(控制副作用在允许的范围)

  • Either 函子 :

    • Either 两者中的任何一个,类似于 if…else…的处理
    • 异常会让函数变的不纯,Either 函子可以用来做异常处理
  • IO函子:

    • IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
    • IO 函子可以把不纯的动作存储到 _value 中,延迟执行这个不纯的操作(惰性执行),包装当前的操作纯
    • 把不纯的操作交给调用者来处理
  • Pointed 函子 :

    • Pointed 函子是实现了 of 静态方法的函子

    • of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文

    • Context(把值放到容器中,使用 map 来处理值)

  • Monad 函子:

    • Monad 函子是可以变扁的 Pointed 函子,IO(IO(x))

    • 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad


结语:以上内容全学习时手敲记录,无复制粘贴,全原创,希望可以给各位小伙伴带来收获,如有错误的地方或有疑问欢迎留言,感谢阅读!


祝各位前端程序猿前程似锦,一路向北!

猜你喜欢

转载自blog.csdn.net/m0_50729201/article/details/113127525