什么是柯里化函数?
把接受多个参数的函数变换成先接受部分参数,并且返回一个接收剩余参数的函数,并由此函数返回最终结果。
如果单单是看这一行定义,可能很难理解什么是柯里化。下面我们来举一个例子,假如我们有一个函数,这个函数用来检测一个人是否超过X岁。
表面看起来是一个很简单的需求,函数伸手就来:
const X = 18;
function overAge(age){
return age > X
}
上面的overAge
函数满足了我们的需求。但是这个函数写的非常烂,因为它需要依赖外部的一个值,这样一来,这个函数的重用性就会变的比较差,你可能会说:我把X
放到函数里面不就行了吗:
function overAge(age){
const X = 18;
return age > X
}
没错,这样似乎好了一点,但是程序里写死了一个18
,这…如果以后想检测是否大于60岁的那不是还得另写一个函数?再改进一下吧:
function overAge(X, age){
return age > X
}
现在我们直接把X
作为参数传给函数了,这个时候想检测大于多少岁的都可以!
overAge(18,33); //true
overAge(18,7); //false
overAge(60,7); //false
overAge(60,77); //true
但是,你是否发现有一些让人不太爽的地方?我如果检测好多个人是否大于18岁,那么每次都要传18这个参数,就显的很奇怪,这是一个相对来说比较固定的值,有没有办法让18只传一次呢?当然有!!!
function overAge(X){
return function(age){
return age > X;
}
}
上面overAge
函数接收一个相对于比较固定的值,然后返回一个函数,这个返回函数里面就是做实际的业务操作。没错,这里形成了一个闭包,里面的X在内部函数调用过后并不会被回收。所以我们接下来可以这样使用:
let over18 = overAge(18);
over18(19);
over18(6);
本来我们需要传两个参数,后面我们通过JS闭包的特性把传参进行优化,其实上面的overAge
就已经是一个,这样写代码从业务逻辑上也是很容易让人理解的。
那么如果是超过丙个参数要如何处理呢?
Lodash里的curry()
Lodash当中有一个curry()
方法。
说到
curry
,我默默的看了一眼桌子上的 Curry ……
官方文档给的是这样一个例子:
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curry(abc);
curried(1)(2)(3);
// => [1, 2, 3]
curried(1, 2)(3);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
// Curried with placeholders.
curried(1)(_, 3)(2);
// => [1, 2, 3]
可以看到,函数abc
经过curry()
之后被柯里化,然后,原本需要传的三个参数,可以通过传给柯里化函数多次的方式进行,而最终得到的结果都是一样的。其实官方这个例子虽然能比较直观的看出curry()
能做到的事,但是不太会清楚为什么要这样做。所以还是拿我们上面的例子来看,如果用Lodash的curry()
该怎么写那个检测年龄的功能呢?
还是之前的函数,传的是两个参数:
function overAge(X,age){
return age>X;
}
然后使用curry()
来进行柯里化:
let overAgeCurry = _.curry(overAge);
let over18 = overAgeCurry(18);
console.log(over18(6)) //false
console.log(over18(19)) //true
怎么样,这样一写是不是就好理解了?因为我们的这个18
是一个相对比较固定的参数,在某种意义上来说,它就相当于一个常量。先进行一次调用,把这个常量封在闭包里,然后再进行后续的传参调用。如果要进行别的年龄判断,那么改变第一次传参即可。
上面的写法好像看起来还是有点不爽,因为多了一个overAgeCurry
函数,其实这一层不需要晾出来,会让人有点不好理解,所以我们可以做这样的修改:
let overAge = _.curry(function (X,age){
return age>X;
});
let over18 = overAge(18);
console.log(over18(6)) //false
console.log(over18(19)) //true
这样,就只有一个表面看起来很好理解的overAge
函数。
curry() 的实现方法
其实柯里化主要是运用函数的闭包特性、函数式的编程思想来实现,下面我们自己来实现一下curry()
函数:
首先,我们来思考一下,这个函数需要什么参数?返回什么参数?从上面的调用来看,传参和返回参数都是一个函数:
function curry(fn){
return function(...args){
}
}
然后,我们需要考虑两种情况:
- 1.如果柯里化后的函数
实参.length >= 形参.length
,那么直接返回fn
的执行结果。 - 2.如果1不满足,那么说明
实参.length < 形参.length
,那么还需要返回另外一个函数来接收剩余的参数。
首先,我们考虑一下情况1,因为比较简单,直接把传过来的参数直接给fn
并且执行并返回就可以了:
function curry(fn){
return function(...args){
//情况2
if(args.length < fn.length){
//...
return ;
}
//情况1
return fn(...args);
}
}
下面我们来考虑一下情况2,这个时候我们还需要继续返回一个函数来进行调用,只不过在调用的时候,要把上次的传参和这次的传参进行一个合并,直到我们把所有参数都传完。所以我们需要来做一个递归调用:
function curry(fn){
//由于需要递归调用,所以给函数取一个名字。
return function curryFn(...args){
//情况2
if(args.length < fn.length){
//...
return function(){
//因为产生了闭包,所以这里的args是之前的参数。
//而arguments,则是这次传的参数。
return curryFn(...args,...arguments);
};
}
//情况1
return fn(...args);
}
}
下面我们来测试一下自己封装的这个curry()
函数:
//去掉 lodash ,调用自己的 curry()>
let overAge = curry(function (X,age){
return age>X;
});
let over18 = overAge(18);
console.log(over18(6)) //false
console.log(over18(19)) //true