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

前端面试手写代码——JS函数柯里化

时间:2023-03-27 12:38:02 JavaScript

1什么是函数柯里化?第一个参数)并返回一个接受剩余参数并返回结果的新函数。这种技术以逻辑学家HaskellCurry的名字命名。什么意思?简单来说,柯里化是一种用于转换具有多个参数的函数的技术。例如://这是一个接受3个参数的函数constadd=function(x,y,z){returnx+y+z}我们可以将它转化为这样一个函数://接收一个参数constcurryingAdd=function(x){//并返回一个接受剩余参数的函数returnfunction(y,z){returnx+y+z}}有什么区别?从调用上对比://calladdadd(1,2,3)//callcurryingAddcurryingAdd(1)(2,3)//看得更清楚,相当于下面的constfn=curryingAdd(1)fn(2,3))可见转换后的函数可以批量接受参数。先记住这一点,下面会讲到它的用处。甚至fn(curryingAdd返回的函数)也可以继续变换:constcurryingAdd=function(x){returnfunction(y){returnfunction(z){returnx+y+z}}}//callcurryingAdd(1)(2)(3)//constfn=curryingAdd(1)constfn1=fn(2)fn1(3)以上两个转换过程都是函数柯里化。简单的说就是把一个多参数函数f转化为一个接受一些参数的函数g,而这个函数g会返回一个函数h,这个函数h用来接受其他的参数。函数h可以继续柯里化。就是套娃过程~那么把函数柯里化那么多有什么用呢?2柯里化的功能和特点2.1参数复用工作中遇到的需求:通过正则化等方式验证电话号码、邮箱、身份证是否合法。因此,我们封装一个验证函数如下:/***@description通过正则模式检查字符串*@param{RegExp}regExp正则对象*@param{String}str要检查的字符串*@return{Boolean}是否通过检查*/functioncheckByRegExp(regExp,str){returnregExp.test(str)}如果我们要验证很多手机号和邮箱,我们会这样调用://验证手机号checkByRegExp(/^1\d{10}$/,'15152525634');checkByRegExp(/^1\d{10}$/,'13456574566');checkByRegExp(/^1\d{10}$/,'18123787385');//验证邮箱checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/,'fsds@163.com');checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/,'fdsf@qq.com');checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/,'fjks@qq.com');好像没什么问题。事实上,仍有改进的余地。在检查同类型数据时,我们多次写同一个正则。代码的可读性很差。如果没有评论,我们是无法一下子看到正则化的效果的。我们尝试使用函数柯里化来改进它://CurryingthefunctionfunctioncheckByRegExp(regExp){returnfunction(str){returnregExp.test(str)}}所以我们传入不同的正则对象,我们可以得到函数不同的功能://检查电话constcheckPhone=curryingCheckByRegExp(/^1\d{10}$/)//检查电子邮件constcheckEmail=curryingCheckByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)现在查手机和邮箱的代码很简单现在,可读性也增强了//查电话号码checkPhone('15152525634');checkPhone('13456574566');checkPhone('18123787385');//查看邮箱checkEmail('fsds@163.com');checkEmail('fdsf@qq.com');checkEmail('fjks@qq.com');这就是参数复用:我们只需要复用第一个参数regExp就可以直接调用Functions通用函数(比如checkByRegExp)解决了兼容性问题,但是也带来了使用上的不便。例如,不同的应用场景需要传递多个不同的参数来解决问题。有时可能会重复使用同一个规则(比如检查手机的参数),从而造成代码重复,可以通过使用柯里化来消除重复,达到参数复用的目的。柯里化的一个重要思想:缩小适用范围,提高适用性,改用attachEvent方法。这个时候我们会写一个兼容各个浏览器版本的代码:/***@description:*@param{object}elementDOM元素对象*@param{string}type事件类型*@param{Function}fn事件处理器function*@param{boolean}isCapture是否捕获*@return{void}*/functionaddEvent(element,type,fn,isCapture){if(window.addEventListener){element.addEventListener(type,fn,isCapture)}elseif(window.attachEvent){element.attachEvent("on"+type,fn)}}我们使用addEvent来添加事件监听,但是每次调用这个方法都会进行判断。其实浏览器版本确定后,就没有重复判断的必要了。Curry处理:functioncurryingAddEvent(){if(window.addEventListener){returnfunction(element,type,fn,isCapture){element.addEventListener(type,fn,isCapture)}}elseif(window.attachEvent){returnfunction(element,type,fn){element.attachEvent("on"+type,fn)}}}constaddEvent=curryingAddEvent()//上面的代码也可以结合立即执行functionconstaddEvent=(functioncurryingAddEvent(){...})()现在我们得到的addEvent是判断后得到的函数,后面调用的时候不需要再重复判断。这是提前返回或提前确认。函数柯里化后,可以提前处理一些任务,返回一个函数来处理其他任务。另外我们可以看到curryingAddEvent好像是不接受参数的。这是因为原函数的条件(即浏览器版本是否支持addEventListener)是直接从global中获取的。逻辑上可以改成:letmode=window.addEventListener?0:1;functionaddEvent(mode,element,type,fn,isCapture){if(mode===0){element.addEventListener(type,fn,isCapture);}elseif(mode===1){element.attachEvent("on"+type,fn);}}//柯里化之后,它可以接受一个参数firstfunctioncurryingAddEvent(mode){if(mode===0){returnfunction(element,type,fn,isCapture){element.addEventListener(type,fn,isCapture)}}elseif(mode===1){returnfunction(element,type,fn){element.attachEvent("on"+type,fn)}}}当然不用这么改了~2.3延迟执行其实上面提到的定时校验和事件监听的例子已经体现了延迟执行。调用curryingCheckByRegExp函数后,调用curringAddEvent函数后返回checkPhone和checkEmail函数。addEvent函数的返回函数不会立即执行,而是等待调用。3通用柯里化工具函数的封装以上我们手动修改了原来柯里化函数的函数,将add改为curryingAdd,将checkByRegExp改为curryingCheckByRegExp,将addEvent改为curryingAddEvent。是不是每次柯里化一个函数都要手动修改底层函数?当然不是,我们可以封装一个通用的柯里化工具函数(面试手写代码)parameterlist*@return{Function}*/constcurrying=function(fn,...args){//fn需要的参数个数constlen=fn.length//返回一个函数,接收剩下的参数returnfunction(...params){//拼接接收到的和新接收到的参数列表let_args=[...args,...params]//如果接收到的参数个数不够,继续返回一个新的一个函数接收剩下的参数if(_args.length