函数式编程-1

214 阅读5分钟

函数式编程

函数式编程-1.png

1.为什么学习函数式编程?

  • react的流行
  • Vue3的拥抱
  • 可以抛弃this
  • 打包中更好的利用tree shaking过滤无用代码
  • 方便测试、方便并行处理
  • 很多库可以帮助我们进行函数式开发:lodash、underscore

2.什么是函数式编程(Functional Programming,FP)?

它是编程范式之一,其他的编程范式还有面向过程、和面向对象

它的思维的方式:把现实世界的事物和事物之间的联系抽象到程序世界中(对运算过程进行的抽象)

注意:

  • 函数式编程中的函数指的不是程序中的函数,而是数学中的函数,即映射关系
  • 相同的输入始终要得到相同的结果
  • 函数式编程用来描述数据(函数)之间的映射

3.为什么说函数是一等公民

  • 可以存储在变量中
  • 可以作为参数
  • 可以作为返回值

其他:函数就是普通的对象

4.什么是高阶函数

可以把函数作为参数传递给另一个函数

可以把函数作为另一个函数的返回结果

//1.函数作为参数
//1.1 forEach
function forEach(array,fn){
    for(let i=0;i<array.length;i++){
        fn(array[i])
    }
}
//filter
function filter(array,fn){
    let results=[]
    for(let i=0;i<array.length;i++){
        if(fn(array[i])){
            results.push(array[i])
        }
    }
    return results
}
//2.函数作为返回值
function makeFn(){
    let msg='Hello function'
    return function(){
        console.log(msg)
    }
}
const fn=makeFn()
//once
function once(fn){
    let done=false
    return function(){
        if(!done){
            done=true
            return fn.apply(this,arguments) 
        }
    }
}
let pay=once(function(money){
    console.log(`支付:${money}RMB`)
})
//只会支付一次
pay(5)
pay(5)

5.使用高阶函数的意义?

  • 用来抽象通用问题
  • 抽象可以帮助我们屏蔽细节,关注目标
//面向过程的方式
let array=[1,2,3,4]
for(let i=0;i<array.length;i++){
    console.log(array[i])
}
//高阶函数
let array=[1,2,3,4]
forEach(array,item=>{
    console.log(item)
})
​
let r=filter(array,item=>{
    return item%2===0
})

6.map、every和some的内部实现?

map ( )遍历每一个元素的同时对,每一个元素进行处理。返回一个新数组

every ( )测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回一个布尔值

some ( )测试数组中是不是至少有一个元素满足函数的要求。返回布尔值

// map
     function map(func,array){
     	let result=[]
     	for(const item of array){
     		result.push(func(item))
     	}
     	return result
     }
     add=item=>item+1
	 console.log(map(add,[1,2,3]))	  
	// every
	  function every(func,array){
	  	let result=true
	  	for(const item of array){
	  		if(!func(item)){
	  			// console.log(func(item),item)
	  			result=false
	  		}
	  	}
	  	return result
	  }
	  compareOne=item=>item>=4
	  console.log(every(compareOne,[1,2,3]))
	  //some
	  function some(func,array){
	  	let result=false
	  	for(const item of array){
	  		if(func(item)){
	  			result=true
	  		}
	  	}
	  	return result
	  }
	  compareTwo=item=>item>=8
	  console.log(some(compareTwo,[1,2,5]))

7.闭包的定义?

在另一个作用域中,调用一个函数的内部函数,并且访问该函数作用域中的成员

8.闭包的本质?

函数在执行时,会放到一个执行栈上

当执行完毕会从执行栈上移除

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

9.什么是纯函数?

相同的输入永远会得到相同的输出,而且没有任何可观察的副作用(类似于数学中的函数)

其他:

函数式编程不会保留计算中间的结果,所以变量式不可变的(无状态的)??

可以把一个函数的执行结果交给另一个函数去处理

10.纯函数的好处?

  • 可缓存——纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来

  • 可测试

  • 可并行处理

    1. 在多线程环境下并行操作共享的数据可能会出现意外情况
    2. 纯函数不需要访问共享的内存数据,所以在并行环境下可以运行纯函数

11.纯函数中的副作用如何产生?有什么后果?

副作用让纯函数变得不纯,纯函数根据相同的输入返回相同的结果,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用

    //不纯的
    let mini=18
    function checkAge(age){
        return age>=mini
    }
    
    //纯的(有硬编码,后续通过柯里化解决)
    function checkAge(age){
        let mini=18
        return age>=mini
    }

副作用来源:

  • 配置文件
  • 数据库
  • 获取用户的输入

副作用的后果?

  • 使方法通用性下降,不适合扩展和可重用性
  • 给程序带来安全隐患

12.柯里化的过程是什么?

  1. 当一个函数有多个参数的时候,先传递一部分参数调用它(这部分参数以后永远不变)
  2. 然后返回一个新的函数,接受剩余的参数,返回结果
    //普通函数
    function checkAge(min,age){
        return age>=min
    }
    checkAge(18,24)
    //柯里化
    function checkage(min){
        return function(age){
            return age>=min
        }
    }
    //Es6写法
    let checkAge=min=>(age=> age>=min)
    
    let checkAge18=checkAge(18)
    let checkAge20=checkAge(20)
    
    checkAge18(24)
    checkAge20(20)
    

13.lodash中的柯里化函数 _.curry(func)如何使用?

功能:

  • 创建一个函数,并接收一个或多个函数作为参数
  • 如果该函数所需要的参数均被提供,那么则执行这个函数并返回结果
  • 否则,继续返回该函数,并等待接受剩余参数

参数:需要柯里化的函数

返回值:柯里化后的函数

    const _=require('lodash')
    //需要柯里化的函数
    function getSum(a,b,c){
        return a+b+c
    }
    //柯里化后的函数
    let curried=_.curry(getSum)
    //测试
    curried(1,2,3)
    curried(1)(2)(3)
    curried(1,2)(3)

案例

    const _=require('lodash')
    const match=_.curry(function(reg,str){
        return str.match(reg)
    })
    
    const haveSpace=match(/\s+/g)
    const haveNumber=match(/\d+/g)
    
    const filter=_.curry(function(func,array){
        return array.filter(func)
    })
    
    console.log(filter(haveSpace,['John Connor','John_Donne']))
    const findSpace=filter(haveSpace)
    console.log(findSpace['John Connor','John_Donne'])

模拟_.curry()的实现

function curry(func){
    return function curriedFn(...args){
        //判断实参和形参的个数
        if(args.length<func.length){
            return function(){
                return curried(...args.concat(Array.from(arguments))
            }
        }
        //实参和形参个数相同,调用func,返回结果
        return func(...args)
    }
}

补充:

  • Array.from()方法对一个类似数组或可迭代对象,创建一个新的、浅拷贝的数组实例
  • concat() 方法用于连接两个或多个数组。

自测

1.什么是函数式编程?它的思维方式是什么?特点是什么?

2.为什么说函数是一等公民?

3.什么是高阶函数?

4.使用高阶函数的意义?

5.map、every和some内部如何实现?

6.闭包的定义,闭包的本质

7.什么是纯函数以及纯函数的好处?

8.柯里化的过程是什么?

9.lodash中的_.curry如何使用?