为什么使用函数式编程
1. React、vue3都支持函数式编程
2. 函数式编程可以抛弃this
3. 打包中可更好利用tree taking过滤没有使用的代码
4. 方便单元测试,方便并行处理
5. 使代码更简洁,更通用,扩展性更好
什么是函数式编程
函数式编程(Functional Programming,FP)是编程范式之一s,与面向对象编程并列
函数式编程的思维方式:对运算过程对抽象
函数式编程的函数是数学中的函数即映射关系,如y=sin(x)
相同输入会得到相同输出(纯函数),如数组的slice方法
函数式编程是描述数据(函数)之间的映射
相关概念
1. 函数是一等公民
函数可存储到变量中,可作为函数的参数,可作为返回值
2. 高阶函数
概念:函数可作为参数传递给其他函数,函数可作为返回值
意义:可抽象具体的细节,而只关注输入与输出,高阶函数可抽象通用问题,更具有扩展性
常用的高阶函数:forEach, map, reduce, every, some, sort, filter, find等
3. 闭包
概念:函数和其周围的状态的引用捆绑在一起形成闭包,简而言之,在里层作用域中使用外层作用域拥有的变量,即形成闭包
本质:函数被放到调用栈执行后,会销毁此函数,但当其中的变量被外部引用时,不会被销毁和释放,故里层函数可使用外层函数的变量
例子:es6的let定义变量会形成闭包,例如:
for ( let i = 0 ; i < 10 ; i++ ) {
setTimeout ( ( ) => {
console. log ( i) ;
} , 10 )
}
此时打印值为1-9,但是如果使用var定义或let定义放到for循环的上方,则不能形成闭包,打印值就为10
4. 纯函数
概念:相同输入会得到相同输出,而且没有任何副作用,所谓副作用就是不可预测的结果,比如函数中使用服务端返回的数据,全局变量等
纯函数类似于数学中的计算公式,比如y=sin(x), x值只要确定,那么y的值就是定值
示例:数组的slice是纯函数,splice不是纯函数
函数式编程不会保留中间过程,只会有返回值,可以把返回值去当参数传给其他函数
好处:
1. 可缓存,因为相同输入有相同输出,所以可以把返回值缓存下来,不用参数相同时,去多次执行
2. 方便自我调试,对超出预期对结果可通过增加断言函数去打印结果
3. 并行处理,因为纯函数只依赖参数,而且不会修改全局变量,所以就算是多线程或多处都调用,也无须担心出现预期外的错误
副作用:会让纯函数变为不纯,比如配置文件,数据库,全局变量,dom
副作用会降低函数的通用性、扩展性、重用性,但副作用不可避免,比如dom交互等
5.柯里化
概念:当函数存在多个参数时,可调用函数时传递一部分参数,返回个新函数,再调用新函数时,传递剩下的参数,当参数全部传入后,再执行此函数
例子:
var _ = require ( "lodash" ) ;
var getSums = ( a, b, c) => { return a + b + c} ;
var getSumsByCurry = _. curry ( getSums) ;
getSumsByCurry ( 1 ) ( 2 ) ( 3 ) ;
手写curry:
var curry = function ( fn) {
return function f ( ... args) {
if ( args. length >= fn. length ) {
return fn ( ... args) ;
}
return function ( ... ab) {
return f ( ... args, ... ab) ;
}
}
}
总结:柯里化可以得到缓存某些公共参数的函数,让函数的粒度更小,更灵活,可以把多个参数的函数变为只传一个参数的函数,方便函数组合时使用
6.函数组合
函数组合可以把多个细粒度的函数组合成新的函数,如果一个函数需要其他函数的返回值作为参数,这时就可以把这些函数组合成一个新的函数
函数式组合可最大限度的去重新利用细粒度的函数,提高函数的重复利用
手写函数组合,从右到左执行
function compose ( ... args) {
return function ( value) {
return args. reverse ( ) . reduce ( function ( ret, fn) {
return fn ( ret)
} , value)
}
}
var fn = compose ( a ( ) , b ( ) ) ;
fn ( 1 )
7.函子(functor)
概念:是一个特殊的容器,由对象实现,该对象必须有map方法,map方法可以对值进行处理
class Container {
static of ( value) {
return new Container ( value)
}
constructor ( value) {
this . _value = value
}
map ( fn) {
return Container. of ( fn ( this . _value) )
}
}
let r = Container. of ( 5 )
. map ( x => x + 2 )
. map ( x => x * x)
总结:
可处理函数对副作用,不在函数内调用副作用对函数,而是暴露出,比如对外暴露run方法,当执行run时,才处理某个传入的函数
map方法会调用传入对函数,返回值为处理过的新的函子
函子就是约定好有map特定作用函数的对象,把传入的value封装,当需要改变value时,通过map方法去实现
类型:
MayBe函子:兼容非空
Either函子:类似if else
IO函子:value为函数,当有需要时,由调用方手动调用
Pointed函子:实现了of方法当函子
Monad函子:使嵌套函子时,把调用方式拍扁