详解JS中的柯里化(currying)
何为Curry化/柯里化?
curry化来源与数学家HaskellCurry的名字(编程语言Haskell也是以他的名字命名)。
柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。
因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程。
柯里化一个求和函数
按照分步求值,我们看一个简单的例子
varconcat3Words=function(a,b,c){ returna+b+c; }; varconcat3WordsCurrying=function(a){ returnfunction(b){ returnfunction(c){ returna+b+c; }; }; }; console.log(concat3Words("foo","bar","baza"));//foobarbaza console.log(concat3WordsCurrying("foo"));//[Function] console.log(concat3WordsCurrying("foo")("bar")("baza"));//foobarbaza
可以看到,concat3WordsCurrying("foo")是一个Function,每次调用都返回一个新的函数,该函数接受另一个调用,然后又返回一个新的函数,直至最后返回结果,分布求解,层层递进。(PS:这里利用了闭包的特点)
那么现在我们更进一步,如果要求可传递的参数不止3个,可以传任意多个参数,当不传参数时输出结果?
首先来个普通的实现:
varadd=function(items){ returnitems.reduce(function(a,b){ returna+b }); }; console.log(add([1,2,3,4]));
但如果要求把每个数乘以10之后再相加,那么:
varadd=function(items,multi){ returnitems.map(function(item){ returnitem*multi; }).reduce(function(a,b){ returna+b }); }; console.log(add([1,2,3,4],10));
好在有map和reduce函数,假如按照这个模式,现在要把每项加1,再汇总,那么我们需要更换map中的函数。
下面看一下柯里化实现:
varadder=function(){ var_args=[]; returnfunction(){ if(arguments.length===0){ return_args.reduce(function(a,b){ returna+b; }); } [].push.apply(_args,[].slice.call(arguments)); returnarguments.callee; } }; varsum=adder(); console.log(sum);//Function sum(100,200)(300);//调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用 sum(400); console.log(sum());//1000(加总计算)
上面adder是柯里化了的函数,它返回一个新的函数,新的函数接收可分批次接受新的参数,延迟到最后一次计算。
通用的柯里化函数
更典型的柯里化会把最后一次的计算封装进一个函数中,再把这个函数作为参数传入柯里化函数,这样即清晰,又灵活。
例如每项乘以10,我们可以把处理函数作为参数传入:
varcurrying=function(fn){ var_args=[]; returnfunction(){ if(arguments.length===0){ returnfn.apply(this,_args); } Array.prototype.push.apply(_args,[].slice.call(arguments)); returnarguments.callee; } }; varmulti=function(){ vartotal=0; for(vari=0,c;c=arguments[i++];){ total+=c; } returntotal; }; varsum=currying(multi); sum(100,200)(300); sum(400); console.log(sum());//1000(空白调用时才真正计算)
这样sum=currying(multi),调用非常清晰,使用效果也堪称绚丽,例如要累加多个值,可以把多个值作为做个参数sum(1,2,3),也可以支持链式的调用,sum(1)(2)(3)
柯里化的作用
- 延迟计算。上面的例子已经比较好低说明了。
- 参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。
- 动态创建函数。
这可以是在部分计算出结果后,在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。例如,事件浏览器添加事件的辅助方法:
varaddEvent=function(el,type,fn,capture){ if(window.addEventListener){ el.addEventListener(type,function(e){ fn.call(el,e); },capture); }elseif(window.attachEvent){ el.attachEvent("on"+type,function(e){ fn.call(el,e); }); } };
每次添加事件处理都要执行一遍if...else...,其实在一个浏览器中只要一次判定就可以了,把根据一次判定之后的结果动态生成新的函数,以后就不必重新计算。
varaddEvent=(function(){ if(window.addEventListener){ returnfunction(el,sType,fn,capture){ el.addEventListener(sType,function(e){ fn.call(el,e); },(capture)); }; }elseif(window.attachEvent){ returnfunction(el,sType,fn,capture){ el.attachEvent("on"+sType,function(e){ fn.call(el,e); }); }; } })();
这个例子,第一次if...else...判断之后,完成了部分计算,动态创建新的函数来处理后面传入的参数,这是一个典型的柯里化。
Function.prototype.bind方法也是柯里化应用
与call/apply方法直接执行不同,bind方法将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数,这符合柯里化特点。
varfoo={x:888}; varbar=function(){ console.log(this.x); }.bind(foo);//绑定 bar(); //888
与call/apply方法直接执行不同,bind方法将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数,这符合柯里化特点。
varfoo={x:888}; varbar=function(){ console.log(this.x); }.bind(foo);//绑定 bar(); //888
下面是一个bind函数的模拟,testBind创建并返回新的函数,在新的函数中将真正要执行业务的函数绑定到实参传入的上下文,延迟执行了。
Function.prototype.testBind=function(scope){ varfn=this;////this指向的是调用testBind方法的一个函数, returnfunction(){ returnfn.apply(scope); } }; vartestBindBar=bar.testBind(foo);//绑定foo,延迟执行 console.log(testBindBar);//Function(可见,bind之后返回的是一个延迟执行的新函数) testBindBar();
这里要注意prototype中this的理解。
实例
实例1:
varcurrying=function(fn){ //fn指官员消化老婆的手段 varargs=[].slice.call(arguments,1); //args指的是那个合法老婆 returnfunction(){ //已经有的老婆和新搞定的老婆们合成一体,方便控制 varnewArgs=args.concat([].slice.call(arguments)); //这些老婆们用fn这个手段消化利用,完成韦小宝前辈的壮举并返回 returnfn.apply(null,newArgs); }; }; //下为官员如何搞定7个老婆的测试 //获得合法老婆 vargetWife=currying(function(){ varallWife=[].slice.call(arguments); //allwife就是所有的老婆的,包括暗渡陈仓进来的老婆 console.log(allWife.join(";")); },"合法老婆"); //获得其他6个老婆 getWife("大老婆","小老婆","俏老婆","刁蛮老婆","乖老婆","送上门老婆"); //换一批老婆 getWife("超越韦小宝的老婆"); 结果: 合法老婆;大老婆;小老婆;俏老婆;刁蛮老婆;乖老婆;送上门老婆 合法老婆;超越韦小宝的老婆 实例2: varcurryWeight=function(fn){ var_fishWeight=[]; returnfunction(){ if(arguments.length===0){ returnfn.apply(null,_fishWeight); }else{ _fishWeight=_fishWeight.concat([].slice.call(arguments)); } } }; varfishWeight=0; varaddWeight=curryWeight(function(){ vari=0;len=arguments.length; for(i;i总结
以上所述是小编给大家介绍的JS中的函数柯里化(currying),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!