什么是柯里化?
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
初始柯里化
在开始进行函数柯里化之前,我们需要有一些前奏知识。下面的代码是一个普通的add函数。但是这个函数有一个缺点:每次我们使用add函数的时候都必须要传入4个完整的参数,那么就会形成参数的冗余。
function add(a: number, b: number, c: number, d: number): number {
return a + b + c + d
}
// 一般来说,越靠前的参数冗余程度越高
add(1, 2, 3, 4)
add(1, 2, 5, 6)
上面的代码中,每次我们想执行add方法时都必须要传入完整的实参列表个数的参数,这样来说非常不方便。所以我们想到了下面一种解决办法。
固定参数的柯里化
所谓固定参数的柯里化就是我们传入一个函数,规定这个函数必需分两步完成,而且两步的加起来的实参列表中的参数必须是函数需要的参数个数。
function fixedParamsCurry(fn: Function, ...rest: number[]) {
return function (...args: number[]) {
// 执行fn并将rest和args两次的实参列表的参数传递给fn
return fn(...rest, ...args)
}
}
let curriedAdd = fixedParamsCurry(add, 1, 2);
console.log(curriedAdd(3, 4));
console.log(curriedAdd(5, 5));
上面的代码复杂化了我们直接使用add函数,但是却方便了后面函数传递参数,而且在一定情况下降低了代码的冗余。但是这并不是我们想要的情况,我们想要的是下面这种传参方式。
curriedAdd(1)(2)(3)(4)
curriedAdd(1, 2, 3, 4)
curriedAdd(1, 2)(3, 4)
curriedAdd(1, 2, 3)(4)
curriedAdd(1)(2, 3)(4)
curriedAdd(1)(2)(3)(4)
上面的代码就很符合我们的情况:我们期望函数一次性能传递过来的参数达到add方法的个数,如果达不到的话没关系,我期望下一次。
根据这个期望关系,我们进入真正的柯里化。
柯里化函数的实现
根据这个期望参数的大小,我们需要对柯里化之后的函数执行时传递参数的个数进行判断,如果这个参数个数和我期望的个数相等的话那么就直接执行这个函数;如果这个参数个数小于我期望的个数,那么我就期望下一次,也就是说我们需要返回一个柯里化的固定参数的函数,用这个函数做一个闭包。
function Curry(fn: Function, length: number = fn.length) {
// length表示当前柯里化时的函数需要的参数个数
return function (...arg: number[]) {
if (arg.length < length) {
// 将固定参数的函数执行之后返回的函数作为柯里化的第一个参数,第二个参数为剩余需要的参数个数
return Curry(fixedParamsCurry(fn, ...arg), length - arg.length)
} else if (arg.length === length) {
return fn(...arg)
}
}
}
let curriedAdd = Curry(add);
console.log(curriedAdd(1)(2)(3)(4));
console.log(curriedAdd(1, 2, 3, 4));
console.log(curriedAdd(1, 2)(3, 4));
console.log(curriedAdd(1, 2, 3)(4));
console.log(curriedAdd(1)(2, 3)(4));
console.log(curriedAdd(1)(2)(3)(4));
柯里化的用途
柯里化用的最广泛的情况就是各大框架中,在实际的例子中用的地方最多为ajax的发送。
// 普通情况下发送ajax
function ajax(methd: string, url: string, data: any) {
console.log(methd + ' ' + url + data)
}
ajax('get', 'http//www.example.com', 'key=2');
ajax('get', 'http//www.example1.com', 'key=2');
ajax('get', 'http//www.example1.com', 'key=3');
上面的代码中,我们可以看到每次我们调用ajax函数的时候需要传入3个参数,而且我们传入的参数实际上出现了冗余。这个时候柯里化就可以简化我们的代码操作。
let curriedAjax = Curry(ajax);
// 得到一个get方法的ajax
let getAjax = curriedAjax('get');
// 传入剩下的需要的参数方法一
getAjax('http://www.exmple.com', 'key=2');
// 方法二
getAjax('http://www.exmple.com')('key=2');
// 得到使用get方法的exmple1的ajax函数
let exmple1Ajax = getAjax('https://www.exmple1.com');
// 传入get方法的http://www.exmple1.com这个ajax函数需要的data
exmple1Ajax('key=2');
exmple1Ajax('key=3');
上面的代码中,可以看出函数的柯里化在很大程度上降低了代码的冗余,而且使用方法更加灵活