详解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),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!