我是一个从事3年的退休前端程序员,何为退休呢,每天准时上下班,工作内容不多难度不大,就这样浑浑噩噩的过了3年。突然一天晚上就感慨,我不能这样,然后在女朋友的唆使下咋就开始了报了拉钩前端高薪训练营,所以在此特意以写笔记记录我的成果,有啥问题欢迎各位大佬积极指出。
为什么学习函数式编程
函数式编程是非常古老的一个概念,早于第一台计算机的诞生,像了解的可以看看这篇文章函数式编程的历史。
函数式编程可以抛弃 this
方便测试、方便并行处理
打包过程中可以更好的利用 tree shaking 过滤无用代码
像我们主流框架,vue 3和react都是拥抱函数式编程的
函数式编程概念
函数式编程中的函数指的不是程序中的函数,而是数学中的函数即映射关系
相同的输入始终要得到相同的输出
函数式编程用来描述数据(函数)之间的映射
简单的代码展示一下
// 非函数式
letnum1=21
letnum2=33
letsum=num1+num2
console.log(sum)
// 函数式
functionadd(n1,n2) {
returnn1+n2
}
letsum=add(21,34)
console.log(sum)
为什么学习函数式编程
函数式编程是非常古老的一个概念,早于第一台计算机的诞生,像了解的可以看看这篇文章函数式编程的历史。
函数式编程可以抛弃 this
方便测试、方便并行处理
打包过程中可以更好的利用 tree shaking 过滤无用代码
像我们主流框架,vue 3和react都是拥抱函数式编程的
函数式编程前置知识
函数是一等公民
高阶函数
闭包
函数是一等公民
函数可以存储在变量中
函数作为参数
函数作为返回值
高阶函数(Higher-order function)
可以把函数作为参数传递给另一个函数
可以把函数作为另一个函数的返回结果
//函数作为参数
// forEach
functionforEach(array,fn) {
for(leti=0;i fn(array[i]) } } forEach([1,0,8], (item)=>{ console.log('forEach==',item) // forEach== 1 // forEach== 0 // forEach== 8 }) // filter functionfilter(array,fn) { letresults=[] for(leti=0;i if(fn(array[i])) { results.push(array[i]) } } returnresults } console.log(filter([1,0,8],item=>item>2))//[8] //函数作为参数 functionmakeFn() { letmsg='Hello makeFn' returnfunction() { console.log(msg) } } constfn=makeFn() fn()//Hello makeFn 闭包 可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员 //函数作为参数 functionmakeFn() { letmsg='Hello makeFn' returnfunction() { console.log(msg) } } constfn=makeFn() fn()//Hello makeFn 就像这行代码,就属于闭包,在全局下调用了makeFn这个函数return出来的函数,而return出来的函数是访问了makeFn函数中的msg变量。 闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是 堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员 //简单案例 constfn=function() { letnum=0; returnfunction() { returnnum+=1; } } letf1=fn() console.log(f1())//1 console.log(f1())//2 通过上述代码可以看出闭包会导致num变量是会一直常驻在内存的,所以会导致一定内存泄露等问题,当也是可以对其进行销毁的,利用javascript垃圾回收(GC)进行销毁 constfn=function() { letnum=0; returnfunction() { returnnum+=1; } } fn()()// 1 fn()()// 1 fn()()// 1 函数式组合 概念 如果一个函数要经过多个函数处理才能的得到最终值 ,这个时候可以把中间过程合并成一个函数 函数就像是数据管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成的最终结果 函数组合默认是从右到左执行的 //函数组合演示 functioncompose(f,g) { returnfunction(value) { returnf(g(value)) } } functionreverse(array){ returnarray.reverse() } functionfirst(array){ retrunarray[0] } constlast=compose(first,reverse) 实现loadsh中组合函数 // const _ = require('loadash') constreverse=arr=>arr.reverse() constfirst=arr=>arr[0] consttoUpper=s=>s.toUpperCase() // const f = _.flowRight(toUpper, first, reverse) functioncompose(...args) { returnfunction(value) { returnargs.reverse().reduce((acc,fn)=>{ returnfn(acc) },value) } } constf=compose(toUpper,first,reverse); console.log(f(['one','two','three'])) 函数组合的结合律(associativity) 我们既可以把g和h组合,还可以吧f和g组合,结果都是一样的 //结合律(associativity) letf=compose(f,g,h) letassociativity=compose(compose(f,g),h)=compose(f,compose(g,h))//true Point Free 我们可以吧数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合到一起,在使用这种模式之前我们需要定义一些辅助函数 不需要执行处理的数据 只需要合成运算过程 需要定义一些辅助的基本运算函数 //非Point Free functionf(word){ returnword.toLowerCase().replace(/\s+/g,'_') } //Point Free constfp=require('lodash/fp') constf=fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower) Functor(函子) 容器:包含值和值的变形关系(这个变形关系就是函数) 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系) //Functor(函子) classContainer{ staticof(value) { returnnewContainer(value) } constructor(value) { this._value=value } map(fn) { returnContainer.of(fn(this._value)) } } letr=Container.of(5) .map(x=>x+2) .map(x=>x*x) console.log(r) 总结: 函数式编程的运算不直接操作值,而是有函子完成 函子就是一个实现了map契约的对象 我们可以把函子想象成一个盒子,这个盒子里面装了一个值 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理 最终map方法返回一个包含新值的盒子(函子) MayBe函子 我们在编程的过程可能会遇到很多错误,需要对这些错误进行相应处理 MayBe函子的作用就是可以对外部的空值情况做处理(控制副作用在可接受范围内) //maybe函子 classMayBe{ staticof(value) { returnnewMayBe(value) } constructor(value) { this._value=value } map(fn) { returnthis.isNothing()?MayBe.of(null) :MayBe.of(fn(this._value)) } isNothing() { returnthis._value===null||this._value===undefined } } letr=MayBe.of(null) .map(x=>x.toUpperCase())console.log(r) Either函子 Either两者中的任何一个,类似于if...else..的处理 异常会让函数变得不纯,Either函子可以用来做异常处理 //either函子 classLeft{ staticof(value) { returnnewLeft(value) } constructor(value) { this._value=value } map(fn) { returnthis } } classRight{ staticof(value) { returnnewRight(value) } constructor(value) { this._value=value } map(fn) { returnRight.of(fn(this._value)) } } functionparseJSON(str) { try{ returnRight.of(JSON.parse(str)) }catch(e) { returnLeft.of({ error:e.message }) }} letr=parseJSON('{name:"zs"}') letr2=parseJSON('{"name":"zs"}') console.log(r) //Left { _value: { error: 'Unexpected token n in JSON at position 1' } }console.log(r2) //Right { _value: { name: 'zs' } } IO函子(输入输出) IO函子的_value是一个函数,这里是把函数作为值来处理 IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),包装当前的操作纯 把不纯的操作交给调用者的来处理 //IO 函子const fp = require('lodash/fp');class IO { static of (value) { return new IO(function () { return value }) } constructor(fn) { this._value = fn } map(fn) { return new IO(fp.flowRight(fn, this._value)) }}//调用let r = IO.of(process).map(p => p.execPath)console.log(r._value()) folktale folktale一个标准的函数式编程库 和lodash、ramda不同的是,他没有提供很多功能函数 只提供了一些函数式的处理操作,例如:compose,curry等,一些函子Task,Either,Maybe等 const{compose,curry}=require('folktale/core/lambda') const{toUpper,first}=require('lodash/fp') //第一个参数是传入函数的参数个数 letf=curry(2,function(x,y){ console.log(x+y)})f(3,4)f(3)(4) //函数组合let f= compose(toUpper, first)f(['one','two']) Task 异步执行 //Task 处理异步任务 constfs=require('fs') const{task}=require('folktale/concurrency/task') const{split,find}=require('lodash/fp') functionreadFile(filename) { returntask(resolver=>{ fs.readFile( filename,'utf-8', (err,data)=>{ if(err)resolver.reject(err) resolver.resolve(data) }) }) } readFile('package.json') .map(split('\n')) .map(find(x=>x.includes('version'))).run() .listen({ onRejected:err=>{console.log(err) }, onResolved:value=>{console.log(value)//"version": "1.0.0", } }) Monad函子 Monad函子是可以变币扁的函子,IO(IO(x)) 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad constfp=require('lodash/fp') // IO Monad classIO{ staticof(x) {returnnewIO(function() {returnx}) }constructor(fn) {this._value=fn}map(fn) {returnnewIO(fp.flowRight(fn,this._value))}join() {returnthis._value() }flatMap(fn) {returnthis.map(fn).join()} }letr=readFile('package.json') .map(fp.toUpper) .flatMap(print) .join() 总结:函数式编程大概这些,后续我还会更新我js的同步,异步编程的笔记和自己的一些看法,再附带一份详细的干货:手写promise源码