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

JavaScript一步一步实现Currying函数

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

简介首先,什么是Currying?简单的说,如果有一个函数接受多个参数,那么一般来说,所有的参数都是一次性传入执行的。对其执行柯里化后,可以多次接收参数。在这里,小编建了一个前端学习交流按钮群:132667127,自己整理的最新前端资料和进阶开发教程。如果愿意,可以进群一起学习交流。第一阶段现在有一个加法函数:函数add(x,y,z){returnx+y+z}被称为add(1,2,3)。如果执行了柯里化,它就变成了curriedAdd()。从效果上来说,大致变成了curriedAdd(1)(2)(3)。现在我们先不看如何对原函数进行柯里化,而是根据这个调用方式重写一个函数。代码可能是这样的:functioncurriedAdd1(x){returnfunction(y){returnfunction(z){returnx+y+z}}}Phase2如果现在想升级,可以接受三个以上的参数.您可以使用参数,或使用扩展运算符来处理传入的参数。但是有一个导数问题。因为之前一次只能传一个,一共只能传三个,保证调用3次后参数个数刚好够用,函数就可以执行了。由于我们打算修改它以接受任意数量的参数,因此我们必须指定一个端点。例如,可以指定当不再传入参数时执行该函数。下面是使用参数的实现。functiongetCurriedAdd(){//外部维护一个数组,用来保存传入的变量letargs_arr=[]//返回一个闭包letclosure=function(){//本次调用传入的参数letargs=Array.prototype.slice.call(arguments)//如果传入了新的参数if(args.length>0){//保存参数args_arr=args_arr.concat(args)//再次返回闭包,等待下一次调用//也可以returnarguments.calleereturnclosure}//没有传递参数,进行累加returnargs_arr.reduce((total,current)=>total+current)}returnclosure}curriedAdd=getCurriedAdd()curriedAdd(1)(2)(3)(4)()复制代码stage3此时可以发现,在上面整个函数中,与函数具体功能相关的部分(这里是执行加法)只有当没有传递参数,其他部分在实现如何多次接收参数。那么,getCurriedAdd只要接受一个函数作为参数,在没有传参的情况下替换这行代码,就可以实现一个通用的柯里化函数。修改上面的代码以实现一个通用的柯里化函数并柯里化一个阶乘函数:concat(args)returnclosure}//没有新参数,执行函数returnfn(...args_arr)}returnclosure}functionmultiply(...args){returnargs.reduce((total,current)=>total*current)}curriedMultiply=currying(multiply)console.log(curriedMultiply(2)(3,4)()上面stage4的代码中,函数执行时机的判断是根据是否传入了参数。但是更多时候,更合理的依据是原函数可以接受的参数总数,函数名的length属性就是函数接受的参数个数。例如:functiontest1(a,b){}functiontest2(...args){}console.log(test1.length)//2console.log(test2.length)//0重写:functioncurrying(fn){letargs_arr=[],max_length=fn.lengthletclosure=function(...args){//先添加参数args_arr=args_arr.concat(args)//如果参数未满,返回闭包并等待一个调用if(args_arr.length[1,2,3]curried(1,2)(3);//=>[1,2,3]curried(1,2,3);//=>[1,2,3]//用占位符柯里化了.curried(1)(_,3)(2);//=>[1,2,3]根据我的理解,curry允许我们:在多个函数调用中逐步收集参数,而不是在一个函数调用中收集一次。当收集到足够的参数时,返回函数执行结果。为了更好的理解,在网上找了多个实现例子。但是,我希望有一个非常简单的教程,从一个基本示例开始,如下所示,而不是直接从最终实现开始。varfn=function(){console.log(arguments);返回fn.bind(null,...arguments);//如果没有es6,我们可以这样写://returnFunction.prototype.bind.apply(fn,[null].concat(//Array.prototype.slice.call(arguments)//));}fb=fn(1);//[1]fb=fb(2);//[1,2]fb=fb(3);//[1,2,3]fb=fb(4);//[1,2,3,4]理解fn函数是一切的起点。基本上,此功能充当“参数收集器”。函数每次被调用时,返回一个自身的绑定函数(fb),并将函数提供的“参数”绑定到返回的函数上。此“参数”将在后续调用返回的绑定函数时提供的任何参数之前。因此,每次调用传递的参数都会逐渐收集到一个数组中。当然,和curry函数一样,我们不用永远收集。现在我们可以先硬编码一个终止点。varnumOfRequiredArguments=5;varfn=function(){if(arguments.length(...args)=>fn.bind(null,...args);上面是一个curry函数,它返回一个“参数收集器”,它只收集一次参数并返回绑定的目标函数。此外,上面的“魔术师”功能仍然没有lodash.js中的“咖喱”功能那么神奇。Lodash的curry允许使用'_'作为输入参数的占位符。咖喱(1)(_,3)(2);//=>[1,2,3],注意占位符'_'对实现占位符函数有一个隐含的要求:我们需要知道的参数是预置给绑定函数的,哪些是额外的参数是显式的在调用函数时提供(这里我们称它们为附加参数)。这个功能可以通过创建另一个闭包来完成:functionfn2(){varpreset=Array.prototype.slice.call(arguments);/*最初:returnfn.bind(null,...arguments);*/returnfunctionhelper(){varadded=Array.prototype.slice.call(arguments);返回fn2.apply(null,[...preset,...added]);//为简单起见,使用es6}}上面的fn2几乎与fn相同,并且功能类似于“参数收集器”。但是,fn2不是直接返回绑定函数,而是返回一个中间辅助函数。辅助函数是未绑定的,因此它可用于将预先设置的参数与以后提供的参数分开。当然,我们在合并的时候需要做一些修改,而不是通过[...preset,...added]将预先设置的参数和后面提供的参数合并。我们需要在预设参数中找到占位符的位置,并用有效的添加参数替换它。我没有看过lodash是如何实现它的,但下面是一个完成类似功能的简单实现。//定义占位符var_='_';functionmagician3(targetfn,...preset){varnumOfArgs=targetfn.length;varnextPos=0;//下一个有效输入位置的索引,可以是'_',也可以是预设的结尾//检查是否有足够的有效参数if(preset.filter(arg=>arg!==_).length===numOfArgs){returntargetfn.apply(null,preset);}else{//返回'helper'函数returnfunction(...added){//循环并将添加的参数添加到预设参数while(added.length>0){vara=added.shift();//获取下一个占位符的位置,可以是'_'或预设的结尾while(preset[nextPos]!==_&&nextPos