百度百科对柯里化的解释:在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
相信没有几个人在看了上面这段解释之后能立马理解函数柯里化是什么东西。通俗一点讲,函数柯里化的主要目的就是为了减少函数传参,同时将一些固定参数私有化。下面展示一段非常简单计算圆面积的代码来说明函数柯里化的原理:
//circle函数,接受半径r和π
function circle(r,p){
//计算半径为r的圆的面积
var area=p*r*r;
return area;
}
/*
* 通过函数柯里化来简化circle函数,只传入半径就能计算出面积
* 不管怎么样,π是不会变的,因此我们将他写死,不需要外部调用传入
*/
function curryCircle(r){
var p=3.14;
var area=p*r*r;
return area;
}
也许你会觉得这段代码很二,但是这就是函数柯里化的真实面目。当然上面的代码只是一个非常小的例子,真实世界中的函数柯里化会比它凶恶一点,下面来讨论一个更通用的例子。假设π不是唯一的(比如我们有三种π),我们计算圆面积公式当中的π会根据场景不同而变化,这个时候我们就不能直接写死,而需要根据不同环境来配置π:
//circle函数,接受半径r和π
function circle(r,p){
//计算半径为r的圆的面积
var area=p*r*r;
return area;
}
//针对circle函数的柯里化函数
function curry(fn,p){
var finalMethod=function(r){
var result=fn(r,p);
return result;
}
return finalMethod;
}
//我们有3种不同的π
var curryCircle1=curry(circle,1.14);
var curryCircle2=curry(circle,2.14);
var curryCircle3=curry(circle,3.14);
//输出:4.56 8.56 12.56
console.log(curryCircle1(2),curryCircle2(2),curryCircle3(2));
可以看到,curry方法通过封装最基础的circle方法,同时保存设置好的p参数(π),并返回一个finalMethod方法,这样我们最终调用finalMethod时就只需要传入参数r(半径)就可以完成。借助函数柯里化,我们拥有了三个简化的计算圆面积方法。上面展示的函数柯里化只能适用于圆面积的计算,这次我们编写一个更通用的柯里化函数:
function curry(fn){
//第一个参数是基础执行方法,slice切除
var args=Array.prototype.slice.call(arguments,1);
//直接返回匿名函数
return function(){
//slice新参数以便能调用concat
var innerArgs=Array.prototype.slice.call(arguments);
//将配置的参数和新传入的参数合并
var finalArgs=args.concat(innerArgs);
return fn.apply(null,finalArgs);
};
}
curry()函数的主要工作就是将被返回函数的参数进行排序。Curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在arguments对象上调用了slice()方法,并传入参数1表示被返回的数组包含从第二个参数开始的所有参数。然后args数组包含了来自外部函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数(又一次使用了slice())。有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将他们合并成finalArgs。最后使用apply()将结果传递给该函数。这样就实现了一个通用的函数柯里化。如果到这此还有余力的读者可以接着往下看,我们将要介绍jQuery中使用的函数柯里化。
在针对某个方法进行柯里化时,我们甚至不用传入fn来告诉柯里化来包装我们的函数,我们可以通过原型直接将函数和柯里化绑定:
Function.prototype.curry=function(){
//利用原型的便利,我们可以直接通过this引用到方法
var fn=this;
var args=Array.prototype.slice.call(arguments);
return function(){
var arg=0;
//循环校验先前传入的参数和新传入的参数是否有差别
for(var i=0;i<args.length && arg<arguments.length;i++){
if(args[i]===undefined){
args[i]=arguments[arg++];
}
}
return fn.apply(this,args);
};
};
与之前不同,我们通过this引用获取了方法引用,这样当我们需要将某个函数柯里化时,只要这样写就可以:
var delay=setTimeout.curry(undefined,10);
delay就是一个已经被提前设定了10毫秒延迟的setTimeout函数。我们仍然通过args来保存参数配置,不过这次有点区别:在for循环内部会校验args和arguments的区别,以此判断来完成参数拼接。所以传给curry的参数必须是完整参数(即意味着不传的值要传入undefined)。最终我们实现了一个不需要传入fn的柯里化方法。