当前位置: 首页 > Web前端 > HTML5

js函数式编程基础:高阶函数、科力花、函数合成、Loadash

时间:2023-04-05 16:18:05 HTML5

一、函数式编程什么是函数式编程(FP)?FP是一种编程范式,一种编程风格,与面向对象并行。FP用于描述数据或函数之间的映射;根据输入,通过某种运算得到对应的输出,即映射关系,例如:y=sin(x)。FP需要有输入和输出,相同的输入有相同的输出(这叫纯函数)。所以我们可以利用这个特性来复用这个函数,达到代码复用的目的。函数式编程的常见应用场景ES6中的map、filter等高阶函数。React的高阶组件是使用高阶函数实现的,这是函数式编程的一个特点。Redux也使用了函数式编程的思想。Vue3也开始拥抱函数式编程,Vue2中也有一些高阶函数。在Webpack的打包过程中,通过treeshaking来过滤掉无用的代码。有很多库可以帮助我们进行功能开发:lodash、underscore、ramda。函数式编程的好处结合了高阶函数、纯函数、科学和化学、函数组合的好处2.高阶函数什么是高阶函数?高阶函数有以下特点:函数作为变量(函数是一等公民特性)函数作为参数,如map、filter、some等(高阶函数特性)函数作为返回值,如闭包、节流、防抖功能等(高阶函数特性)使用高阶函数有什么好处?高阶函数可以帮助我们对一些流程代码进行复用和抽象,使得代码的复用性更高。常用的高阶函数?forEachmapfiltereverysomefind/findIndexreducesort例如:map,some,函数节流防抖等constmap=(array,fn)=>{letresults=[]for(constvalueofarray){results.push(fn(value))}returnresults}//testletarr=[1,2,3,4]arr=map(arr,v=>v*v)console.log(arr)//some判断是否有元素满足我们指定条件的数组满足则为真,不满足则为假constsome=(array,fn)=>{letresult=falsefor(constvalueofarray){result=fn(value)//如果有元素不满足,直接跳出loopif(result){break}}returnresult}//testletarr=[1,3,4,9]letarr1=[1,3,5,9]letr=some(arr,v=>v%2===0)console.log(r)//true=some(arr1,v=>v%2===0)console.log(r)//false//once//函数节流,let函数只执行一次functiononce(fn){letdone=falsereturnfunction(){//判断值是否已经执行。如果为false,说明还没有执行。如果为真,则表示已执行。if(!done){done=true//调用fn,目前This直接传递,第二个参数是传递fn参数返回的函数returnfn.apply(this,arguments)}}}//testletpay=once(function(money){console.log(`Payment:${money}RMB`)})pay(5)//Payment:5RMBpay(5)pay(5)pay(5)pay(5)3.纯函数什么是纯函数?相同的输入有相同的输出且没有副作用,普通的纯函数切片纯函数必须有输入和输出letnumbers=[1,2,3,4,5]//purefunction//对于同一个函数,output是一样的//slice方法在截取的时候返回截取的函数,不影响原数组numbers.slice(0,3)//=>[1,2,3]numbers.slice(0,3)//=>[1,2,3]numbers.slice(0,3)//=>[1,2,3]//非纯函数//对于相同的输入,输出不同//拼接方法,返回原始数组,改变原始数组numbers.splice(0,3)//=>[1,2,3]numbers.splice(0,3)//=>[4,5]numbers.splice(0,3)//=>[]纯函数有什么好处:可以缓存(根据相同的传入参数,比如Loadash中的memoize()方法)可以测试并行处理多线程中并行操作共享的内存数据线程环境很可能出乎意料纯函数不需要访问共享内存数据,因此纯函数可以在并行环境中任意运行。(虽然JS是单线程的,但是ES6之后有了WebWorker,可以开一个新的线程)函数的副作用怎么理解?如果一个函数依赖于外部状态,它不能保证相同的输出,这会带来副作用,如下例所示://impurefunctionbecauseitdependsexternalvariablesletmini=18functioncheckAge(age){returnage>=mini}副作用的来源是什么?配置文件数据库获取用户的输入...4.功能合理化什么是功能合理化?柯里化是一种将具有多个参数的函数转换为单个参数的函数。柯里化可以缓存函数参数//柯里化示例const_=require('lodash')functiongetSum(a,b,c){returna+b+c}constcurried=_.curry(getSum)console.log(curried(1,2,3))//6console.log(curried(1)(2,3))//6console.log(curried(1,2)(3))//6科理化模拟实现//写法一:functioncurry(fn){function_c(restNum,argsList){returnrestNum===0?//fn.apply(null,argsList)fn.apply(null,argsList):function(x){return_c(restNum-1,argsList.concat(x));};}return_c(fn.length,[]);}//使用varplus=curry(function(a,b){returna+b;});//3console.log(plus(1)(2));输入参数和输出参数:调用传递一个纯函数参数,返回一个柯里化函数完成后分析输入参数:如果柯里化调用传递的参数与getSum函数参数相同,则立即执行,如果返回调用结果咖喱电话传递的参数是getSum函数的一部分参数,所以需要返回一个新的函数,等待接收getSum的其他参数重点:获取调用的参数判断是否相同//写法二:模拟curry函数functioncurry(func){//返回函数取名curriedFn(...args){//确定实参和形参个数if(args.lengthvalue=>args.reverse().reduce((acc,fn)=>fn(acc),value)6.常用的函数式编程库Loadash,柯立华/函数合成比较简单(重要)学习了函数式编程的概念后,最重要的是我们会用到Loadash库进行一些简单的应用。参考资料:Loadash官网LoadashInstall>npmi--savelodashLoadash常用方法curry(),创建一个接收一个或多个func参数的函数,如果提供了func需要的所有参数,则执行func并返回执行结果。否则,继续返回函数,等待接收剩余的参数。flow()组合多个函数,通过从左到右运行flowRight()来组合多个函数。从右往左运行,使用较多(注意:上一个函数执行后的结果需要作为下一个函数的入参,数据类型也必须以匹配为主;)memoize()可以缓存函数执行,同样的参数使用缓存//柯里化curry()在Loadash中的例子const_=require('lodash')//一个参数是一元函数,两个是二元函数//柯里化可以将多元函数转换成一元函数functiongetSum(a,b,c){returna+b+c}//定义一个柯里化函数constcurried=_.curry(getSum)//如果所有参数都输入,则立即返回结果console.log(curried(1,2,3))//6//如果传入了部分参数,则返回当前函数,等待接收getSum中剩余的参数console.log(curried(1)(2,3))//6console.log(curried(1,2)(3))//6//Loadash中的FlowRight()例子:下面的例子是获取数组的最后一个元素并将其转换为大写字母const_=require('lodash')constreverse=arr=>arr.reverse()constfirst=arr=>arr[0]consttoUpper=s=>s.toUpperCase()//注意:前面函数执行后的结果需要它是后一个函数的输入参数;数据类型也要以匹配为主;constf=_.flowRight(toUpper,first,reverse)console.log(f(['one','two','three']))//THREE//Loadash中的memoize()示例:const_=require('lodash')functiongetArea(r){console.log(r)returnMath.PI*r*r}letgetAreaWithMemory=_.memoize(getArea)console.log(getAreaWithMemory(4))console.log(getAreaWithMemory(4))Memory(4))console.log(getAreaWithMemory(4))//4//50.26548245743669//50.26548245743669//50.26548245743669//输出4只执行一次,因为结果被缓存了//自定义实现memoize()缓存函数functionmemoize(f){letcache={}returnfunction(){//arguments是一个伪数组,所以需要进行字符串转换letkey=JSON.stringify(arguments)//如果缓存中有值的话,赋值,如果没有值,调用f函数并将参数传递给它cache[key]=cache[key]||f.apply(f,arguments)返回缓存[key]}}letgetAreaWithMemory1=memoize(getArea)console.log(getAreaWithMemory1(4))console.log(getAreaWithMemory1(4))console.log(getAreaWithMemory1(4))//4//50.26548245743669//50.26548245743669//50.26548245743669//50.26548245743669//50.26548245743669//其他常用方法:参考七种方法:Load69https://www.comlodashjocase/convert/lowerdashjcase/:_.toLower("NERVER"])//"nerver"//拆分字符串,最多显示limit_.split("nerver-say-die","-",[limit])//["nerver","say","die"]//将数组组合成字符串_.join(["nerver","say","die"],"-")//"nerver-say-die"//类似到ES6的map,参数位置不同_.map(array,item=>console.log(item))八、关于flowRight的使用和调试(函数组合)//NEVERSAYDIE-->nerve-say-dieconst_=require('lodash')constsplit=_.curry((sep,str)=>_.split(str,sep))constjoin=_.curry((sep,array)=>_.join(array,sep))//我们需要打印中间值并知道它的位置,用curry输出constlog=_.curry((tag,v)=>{console.log(tag,v)returnv})//从右到左在每个函数后面加一个log,传入tag的值,就可以知道每次输出的是什么。constf=_.flowRight(join('-'),log('aftertoLower:'),_.toLower,log('aftersplit:'),split(''))//从右到左//第一条日志:aftersplit:['NEVER','SAY','DIE']Correct//第二条日志:aftertoLower:never,say,die转换为小写字母时,也转换为字符串。这里控制台有问题。log(f('NEVERSAYDIE'))//n-e-v-e-r-,-s-a-y-,-d-i-e//修改方法,使用数组的map方法遍历数组的每个元素,使其小写//这里的map需要两个参数,第一个是数组,第二个是回调函数,需要柯里化constmap=_.curry((fn,array)=>_.map(array,fn))//split('')(str),入参为字符串,返回数组//map(_.toLower)(array),入参为数组,返回数组//join('-'),入参是一个数组,返回一个字符串constf1=_.flowRight(join('-'),map(_.toLower),split(''))console.log(f1('NEVERSAYDIE'))//never-说死九,LoadshFPin模块1,FP模块中的函数调整参数的位置,遵循数据的特点,函数优先;即函数通常放在参数2的前面,FP模块中的函数都是有理化的,方便函数组合//lodashModuleconst_=require('lodash')//datafirst,functionlast_.map(['a','b','c'],_.toUpper)//=>['A','B','C']_.map(['a','b','c'])//=>['a','b','c']//数据优先,规则_.split('HelloWorld','')//但是//lodash/fpmoduleconstfp=require('lodash/fp')//函数在前,数据在后fp.map(fp.toUpper,['a','b','c'])fp.map(fp.toUpper)(['a','b','c'])//规则在前,数据在后fp.split('','HelloWorld')fp.split('')('HelloWorld')constfp=require('lodash/fp')constf=fp.flowRight(fp.join('-'),fp.map(fp.toLower),fp.split(''))console.log(f('从不SAYDIE'))//永不言败特别鸣谢:拉勾教育前端高薪训练营