定义
柯里化是一种关于函数的高阶技术。
它指的是一种函数的转换,即将一个函数从可调用的f(a, b, c)
转换为可调用的f(a)(b)(c)
。我们必须要明确的是,柯里化不会调用函数,它只是对函数进行转换。
举个例子:
function curry(f) { // curry(f) 执行柯里化转换
return function(a) {
return function(b) {
return f(a, b);
};
};
}
function add(a, b) {
return a + b;
}
let curriedSum = curry(add);
console.log(curriedSum(1)(2)); // 3
正如上述代码所示,实现是通过两个包装器:
1. `curry(func)`的结果为一个包装器`function(a)`;
2. 当其被`curriedSum(1)`调用时,它的参数被保存到词法环境中,然后返回一个新的包装器`function(b)`;
3. 然后该包装器以参数`2`被调用,并且将调用传递给`add`函数。
因此,柯里化可以这样子来理解:用闭包将参数保存起来,当参数的数量足够去执行函数后,我们就执行函数。
用途
可是也有人问了,柯里化到底能干什么?
此处假设一家便利店给优惠顾客10%的折扣。
function discount(price, discount) {
return price * (1 - discount);
}
// 如果购买了价值100元的物品,折扣后价格为:90
const price = discount(100, 0.10);
如果对每一个优惠顾客都要计算10%的折扣,这会不会很麻烦?此时我们就可以用到柯里化,这样我们就可以不用每次都增加这10%的折扣。
function discount(discount) {
return (price) => {
return price * (1 - discount);
}
}
const tenPercentDiscount = discount(0.1);
// 此后可以直接调用 tenPercentDiscount 函数,传入价格
tenPercentDiscount(200); // 180
而当我们想要修改折扣时,可以去修改discount
函数,传入新的折扣值即可实现复用。
由此可见,柯里化的用途可以理解为:参数复用。本质上就是降低通用性,提高适用性。
柯里化实现
话不多说,先上代码:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
}
}
当我们要运行该函数时,会有以下两个分支:
- 如果传入的
args
长度与原始函数所定义的参数长度即func.length
相等或更长,则直接利用apply()
g改变this
指向,传入参数后调用原始函数; - 否则我们将获得一个偏函数,返回一个包装器,它将之前传入的参数和新参数一起传入后应用
curried
.
此处补充一个优雅写法:
const curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg)