一起来聊聊JavaScript函数柯里化

本篇文章给大家带来了关于javascript中的相关知识,其中主要介绍了JavaScript中函数柯里化的相关问题,柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返...

本篇文章给大家带来了关于javascript中的相关知识,其中主要介绍了JavaScript中函数柯里化的相关问题,柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术,希望对大家有帮助。

一起来聊聊JavaScript函数柯里化插图1

一、简单了解apply和call

call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。

call 和 apply二者的作用完全一样,只是接受参数的方式不太一样。call其实是apply的一种语法糖。

格式:apply(context,[arguments]),call(context,param1,param2,...)。

二、什么是函数柯里化?

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

在这里举个例子,有一个add()函数,它是用来处理我们传给它的参数(param1,params2,…)相加求和的一个函数。

//在这里第一个具有两个参数`x`、`y`的`add(x,y)`函数functionadd(x,y){returnx+y;}//调用`add()`函数,并给定两个参数`4`和`6`add(4,6);//模拟计算机操作,第一步传入第一个参数4functionadd(4,y){return4+y;}//模拟计算机操作,第二步传入第一个参数6functionadd(4,6){return4+6;}

如果我们将add()函数柯里化,是什么样子呢?在这里简单的实现一下:

//柯里化过的add()函数,可以接受部分参数functionadd(x,y){if(typeofy==='undefined'){returnfunction(newy){returnx+newy;}}//完整应用returnx+y;}//测试调用console.log(typeofadd(4));//[Function]console.log(add(4)(6));//10//可以创建保存函数letsaveAdd=add(4);console.log(saveAdd(6));//10

从以上简单柯里化的add()函数可以看出,函数可以接受部分函数,然后返回一个新的函数,使其继续处理剩下的函数。

三、写一个公共的柯里化函数

在这里我们创建一个公共的柯里化函数,那样我们就不必每次写一个函数都要在其内部实现复杂的柯里化过程。

//定义一个createCurry的函数functioncreateCurry(fn){varslice=Array.prototype.slice,stored_args=slice.call(arguments,1);returnfunction(){letnew_args=slice.call(arguments),args=stored_args.concat(new_args);returnfn.apply(null,args);}}

在以上公共的柯里化函数中:

arguments,并不是一个真的数组,只是一个具有length属性的对象,所以我们从Array.prototype中借用slice方法帮我们把arguments转为一个真正的数组,方便我们更好的操作。

当我们第一次调用函数createCurry的时候,其中变量stored_args是保持了除去第一个参数以外的参数,因为第一个参数是我们需要柯里化的函数。

当我们执行createCurry函数中返回的函数时,变量new_args获取参数并转为数组。

内部返回的函数通过闭包访问变量stored_args中存储的值和变量new_args的值合并为一个新的数组,并赋值给变量args。

最后调用fn.apply(null,args)方法,执行被柯里化的函数。

现在我们来测试公共的柯里化函数

//普通函数add()functionadd(x,y){returnx+y;}//柯里化得到一个新的函数varnewAdd=createCurry(add,4);console.log(newAdd(6));//10//另一种简便方式console.log(createCurry(add,4)(6));//10

当然这里并不局限于两个参数的柯里化,也可以多个参数:

//多个参数的普通函数functionadd(a,b,c,d){returna+b+c+d;}//柯里化函数得到新函数,多个参数可以随意分割console.log(createCurry(add,4,5)(5,6));//20//两步柯里化letadd_one=createCurry(add,5);console.log(add_one(5,5,5));//20letadd_two=createCurry(add_one,4,6);console.log(add_two(6));//21

通过以上的例子,我们可以发现一个局限,那就是不管是两个参数还是多个参数,它只能分两步执行,如以下公式:

fn(x,y) ==> fn(x)(y);

fn(x,y,z,w) ==> fn(x)(y,z,w) || fn(x,y)(z,w)||…

如果我们想更灵活一点:

fn(x,y) ==> fn(x)(y);

fn(x,y,z) ==> fn(x,y)(z) || fn(x)(y)(z);

fn(x,y,z,w) ==> fn(x,y)(z)(w) || fn(x)(y)(z)(w) || …;

我们该怎么实现呢?

四、创建一个灵活的柯里化函数

经过以上练习,我们发现我们创建的柯里化函数存在一定局限性,我们希望函数可以分为多步执行:

//创建一个可以多步执行的柯里化函数,当参数满足数量时就去执行它://函数公式:fn(x,y,z,w)==>fn(x)(y)(z)(w);letcreateCurry=(fn,...params)=>{letargs=parsms||[];letfnLen=fn.length;//指定柯里化函数的参数长度return(...res)=>{//通过作用域链获取上一次的所有参数letallArgs=args.slice(0);//深度拷贝闭包共用的args参数,避免后续操作影响(引用类型)allArgs.push(...res);if(allArgs.length<fnLen){//当参数数量小于原函数的参数长度时,递归调用createCurry函数returncreateCurry.call(this,fn,...allArgs);}else{//当参数数量满足时,触发函数执行returnfn.apply(this,allArgs);}}}//多个参数的普通函数functionadd(a,b,c,d){returna+b+c+d;}//测试柯里化函数letcurryAdd=createCurry(add,1);console.log(curryAdd(2)(3)(4));//10

以上我们已经实现了灵活的柯里化函数,但是这里我们又发现了一个问题:

如果我第一次就把参数全部传入,但是它并没有返回结果,而是一个函数(function)。

只有我们再次将返回的函数调用一次才能返回结果:curryAdd(add,1,2,3,4)();

可能有人说如果是全部传参,就调用原来的add()函数就行了,这也是一种办法;但是我们在这里既然是满足参数数量,对于这种情况我们还是处理一下。

在这里我们只需要在返回函数前做一下判断就行了:

letcreateCurry=(fn,...params)=>{letargs=parsms||[];letfnLen=fn.length;//指定柯里化函数的参数长度if(length===_args.length){//加入判断,如果第一次参数数量以经足够时就直接调用函数获取结果returnfn.apply(this,args);}return(...res)=>{letallArgs=args.slice(0);allArgs.push(...res);if(allArgs.length<fnLen){returncreateCurry.call(this,fn,...allArgs);}else{returnfn.apply(this,allArgs);}}}

以上可以算是完成了一个灵活的柯里化的函数了,但是这里还不算很灵活,因为我们不能控制它什么时候执行,只要参数数量足够它就自动执行。我们希望实现一个可以控制它执行的时机该怎么办呢?

五、写一个可控制的执行时间的柯里化函数

我们这里直接说明一下函数公式:

fn(a,b,c) ==> fn(a)(b)(c )();

fn(a,b,c) ==> fn(a);fn(b);fn(c );fn();

当我们参数足够时它并不会执行,只有我们再次调用一次函数它才会执行并返回结果。在这里我们在以上例子中加一个小小的条件就可以实现。

//当参数满足,再次执行时调用函数letcreateCurry=(fn,...params)=>{letargs=parsms||[];letfnLen=fn.length;//指定柯里化函数的参数长度//当然这里的判断需要注释掉,不然当它第一次参数数量足够时就直接执行结果了//if(length===_args.length){//加入判断,如果第一次参数数量以经足够时就直接调用函数获取结果//returnfn.apply(this,args);//}return(...res)=>{letallArgs=args.slice(0);allArgs.push(...res);//在这里判断输入的参数是否大于0,如果大于0在判断参数数量是否足够,//这里不能用&&,如果用&&也是参数数量足够时就执行结果了。if(res.length>0||allArgs.length<fnLen){returncreateCurry.call(this,fn,...allArgs);}else{returnfn.apply(this,allArgs);}}}//多个参数的普通函数functionadd(a,b,c,d){returna+b+c+d;}//测试可控制的柯里化函数letcurryAdd=createCurry(add,1);console.log(curryAdd(2)(3)(4));//functionconsole.log(curryAdd(2)(3)(4)());//10console.log(curryAdd(2)(3)());//当参数不足够时返回NaN

产品猿社区致力收录更多优质的商业产品,给服务商以及软件采购客户提供更多优质的软件产品,帮助开发者变现来实现多方共赢;

日常运营的过程中我们难免会遇到各种版权纠纷等问题,如果您在社区内发现有您的产品未经您授权而被用户提供下载或使用,您可按照我们投诉流程处理,点我投诉

本文来自用户发布投稿,不代表产品猿立场 ;若对此文有疑问或内容有严重错误,可联系平台客服反馈;

部分产品是用户投稿,可能本文没有提供官方下下载地址或教程,若您看到的内容没有下载入口,您可以在我们产品园商城搜索看开发者是否有发布商品;若您是开发者,也诚邀您入驻商城平台发布的产品,地址:点我进入

如若转载,请注明出处:http://www.chanpinyuan.cn/33458.html;
(0)
上一篇 2023年1月17日 下午4:17
下一篇 2023年1月17日 下午4:17

相关推荐

发表回复

登录后才能评论
分享本页
返回顶部