函数式编程

205 阅读5分钟

函数式编程

为什么要学习函数式编程

. 函数式编程是随着react的流行受到越来越多的关注
. vue3.0也开始拥抱函数式编程
. 函数编程可以抛弃this . 打包过程中可以更好的利用tree shaking 过滤无用的代码
. 方便测试,方便并行处理
. 有很多的库可以帮助我们进行函数式开发:lodash nuderscore ramda

函数式编程的概念

函数式编程(Function Programming,FP) FP是编程范式之一,我们常听的编程范式还有面象过程编程、面象对象编程。
. 面象对象编程的思维方式: 把现实世界的事物抽象成程序世界中的类和对象,通过封装,继承和多态来演示事物事件的联系
. 函数式编程的思维方式:把现实世界的事物和事件之间的联系抽象到程序世界(对运算过程进行抽象)
1:函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数既映射关系,例如y=sin(x),x和y的关系。
2: 相同的输入始终要的到相同的输出
3: 函数式编程用来描述(函数)之间的映射

	//非函数式
    let num1 = 2 
    let num2 = 3 
    let sum = num1 + num3 
    console.log(sum)
    
    //函数式
    function  add(n1,n2){
    	return n1 + n2
    }
    let sum = add(2,3)
    console.log(sum)

函数式一等公民 (MDN First-calss Function)

1: 函数可以存储在变量中
2: 函数可以作为参数
3: 函数可以作为返回值
在javascript中函数就是一个普通的对象(可以通过new Function()),我们可以把函数储存到变量/数组中,他还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过new Function(alert('hello world'))来构造新的函数。

   //把函数赋值给变量
   let fn = function(){
   		console.log('hello world')
   }
   fn()
   //一个示例
   const BlogController = {
    index(posts){return views.index(posts)},
    show(post){return views.show(post)},
    create(attrs){return views.create(attrs)},
    undate(post,attrs){return Db.undate(post,attrs)}
}
  //优化
  const BlogController = {
      index:views.index,
      show:views.show,
      create:views.create,
      undate:Db.undate
  }

高阶函数

什么是高阶函数?
高阶函数(height-order-function)
1:可以把函数作为参数传递给另一个函数
2: 可以把函数作为另一个函数的返回结果

 	//高阶函数-函数作为参数
function forEach(array,fn){
    for(let i =0;i<=array.length;i++){
        fn(array[i])
    }
}
// 测试
let arr = [1,3,5,8,9]
forEach(arr,function(value){
    console.log(value)//1 3 5 8 9 
})
function filter(array,fn){
    let result = [];
    for(let i = 0 ; i <= array.length; i++ ){
        console.log(fn(array[i]))
        if(fn(array[i])){
            result.push(array[i])
        }
    }
    return result
}
let res = filter(arr,function(value){
  return value >= 5 
})
console.log(res)

通过以上代码我们可以看出函数作为参数的好处有:
他可以让我们的函数变得更加灵活,我们在调用forEach的时候我们不需要考虑它内部是怎么来实现的,这个函数把我们内部实现的细节都屏蔽了

函数作为返回值

//写一个只执行一次的函数
function one (fn){
let done = false
return function(){
    if(!done){
        done = true
        return fn.apply(this,arguments)
    }       
}
}

let pay = one(function(money){
    console.log(`支付了 :${money} ¥`)
})

pay(5)
pay(5)
pay(5)
pay(5)
pay(5)

这个时候很多就会说我们写一个函数不就为了让他重复利用吗让他只执行一次不是没有意义吗 其实有的时候用户在付款的时候点击了多下我们就会让他支付一次款,这个时候就可以用到了

高级函数的意义

1: 抽象可以帮我们屏蔽细节,我们只需要关注与我们目标
2: 高阶函数是用来抽象通用的问题

常用的高阶函数

forEach map filter every some find findeIndex reduce sort ...... 这些方法都有一个共同的特点他们都是用函数来做参数

//模拟常用高阶函数map every some
const map = function(array,fn){
let result = [];
for(value of array){
    result.push(fn(value))
} 
return result  
}
let arr = [5,6,8]
// 求每一项的平方
let r1 = map(arr,v=>v*v)
console.log('map',r1)

const every = function(array,fn){
    let isok = false
    for(value of array){
        if(fn(value)){
            isok=true
        }
    }
    return isok 
}
// 数组中每一项是不是都大于2
let r2 = every(arr,(value)=>{
    return value > 2
})
console.log('every',r2)


const some = (array,fn)=>{
    let result = false 
    for( value of array){
        if(fn(value)){
            result = true
            break;
        }
    }
    return result
}
// 数组中是否有一项附和小于7
let r3= some(arr,(value)=>value<7)
console.log("some",r3)

闭包

闭包的本质:函数在执行的时候会放在一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员

纯函数概念

纯函数:相同的输入总会得到相同的输出,而且没有任何可观察的副作用
纯函数就类似数学中的函数(用来描述输入和输出的关系),y=f(x)

数组的中的slice 和 splice 就纯函数和不纯的函数
slice返回数组中指定部分,不会改变原数组
splice对数组进行操作返回该数组,会改变原数组

所以我们可以得出以下结果
1 函数式编程不会保留中间计算,所以变量是不可变的(无状态的)
2 我们可以把一个函数的执行结果交给另一个函数去处理

纯函数的好处

1可缓存 (因为纯函数相同的输入始终有相同的结果,所以我们可以把纯函数的结果缓存起来)

同上述打印结果可以看出当我们第一次去求圆的面试的时候打印了圆的半径,后面几次执行的函数都是从缓存里面读取的
缓存一半用在的地方
如果我们项目有一个函数,这个函数需要多次调用并且每次执行起来特别耗时,这样我们可以通过计算把结果缓存起来
2 可测试 (纯函数让测试方便)
3 并行处理
.在多线程环境下并行操作共享的内存数据很可能会出现意外得到情况
.纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数(web worke 开启多线程)

柯里化

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

	//做一个校验年龄是否大于18的函数
 //柯里化
 function checkAge(min){
 	return function(age){
     	return age>= min
     }
 }
 //es6 写法
 const checkAag = min=>(age=> age>=min)
 

总结函数的柯里化
柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
这是一种函数的缓存
让函数更加灵活,让函数的粒度更小
可以把多元函数转化成一元函数,可以组合使用函数产生强大的功能

组合函数的感念

纯函数和柯里化容易写出洋葱代码 例如:h(g(f(x)))
函数组合可以让我们把细粒度的函数重新组合生成一个新函数
函数组合(compose)用的地方
如果一个函数需要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
函数就想数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
函数组合默认是从右到左执行

//需求先把数组翻转在获取数组第一个元素
// 函数组合演示
function compose (f,g){
   return function(value){
       return f(g(value))
   }
}
function reverse(array){
   return array.reverse()
}
function  first(array) {
   return array[0]
}
let last = compose(first,reverse)
console.log(last([1,2,3,4])) //4

组合函数原理模拟

function compose (...ages) {
return function(value){
   return ages.reverse().reduce(function(acc,fn){
       return fn(acc)
   },value)
}
}
//es6写法
const compose = (...ages)=>value=>ages.reverse().reduce((acc,fn)=>fn(acc),value)

functor(函子)

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

// functor 函子
class container {
static of (value){
    new container(value)
}
constructor (value){
    this._value = value
}
map(fn){
    return container.of(fn(this._value))
}
}

总结
函数式编程的运算不直接操作值,而是由函子完成
函子就是一个实现了map契约的对象
我们可以把函子想象成一个盒子,这个盒子里封装了一个值
想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
最终map方法返回包含新值的盒子(函子)