定义

柯里化是一种关于函数的高阶技术。
它指的是一种函数的转换,即将一个函数从可调用的f(a, b, c)转换为可调用的f(a)(b)(c)。我们必须要明确的是,柯里化不会调用函数,它只是对函数进行转换。
举个例子:

  1. function curry(f) { // curry(f) 执行柯里化转换
  2. return function(a) {
  3. return function(b) {
  4. return f(a, b);
  5. };
  6. };
  7. }
  8. function add(a, b) {
  9. return a + b;
  10. }
  11. let curriedSum = curry(add);
  12. console.log(curriedSum(1)(2)); // 3
  1. 正如上述代码所示,实现是通过两个包装器:
  2. 1. `curry(func)`的结果为一个包装器`function(a)`;
  3. 2. 当其被`curriedSum(1)`调用时,它的参数被保存到词法环境中,然后返回一个新的包装器`function(b)`;
  4. 3. 然后该包装器以参数`2`被调用,并且将调用传递给`add`函数。

因此,柯里化可以这样子来理解:用闭包将参数保存起来,当参数的数量足够去执行函数后,我们就执行函数。

用途

可是也有人问了,柯里化到底能干什么?
此处假设一家便利店给优惠顾客10%的折扣。

  1. function discount(price, discount) {
  2. return price * (1 - discount);
  3. }
  4. // 如果购买了价值100元的物品,折扣后价格为:90
  5. const price = discount(100, 0.10);

如果对每一个优惠顾客都要计算10%的折扣,这会不会很麻烦?此时我们就可以用到柯里化,这样我们就可以不用每次都增加这10%的折扣。

  1. function discount(discount) {
  2. return (price) => {
  3. return price * (1 - discount);
  4. }
  5. }
  6. const tenPercentDiscount = discount(0.1);
  7. // 此后可以直接调用 tenPercentDiscount 函数,传入价格
  8. tenPercentDiscount(200); // 180

而当我们想要修改折扣时,可以去修改discount函数,传入新的折扣值即可实现复用。
由此可见,柯里化的用途可以理解为:参数复用。本质上就是降低通用性,提高适用性。

柯里化实现

话不多说,先上代码:

  1. function curry(func) {
  2. return function curried(...args) {
  3. if (args.length >= func.length) {
  4. return func.apply(this, args);
  5. } else {
  6. return function(...args2) {
  7. return curried.apply(this, args.concat(args2));
  8. }
  9. }
  10. }
  11. }
  1. 当我们要运行该函数时,会有以下两个分支:
  1. 如果传入的args长度与原始函数所定义的参数长度即func.length相等或更长,则直接利用apply()g改变this指向,传入参数后调用原始函数;
  2. 否则我们将获得一个偏函数,返回一个包装器,它将之前传入的参数和新参数一起传入后应用curried.

此处补充一个优雅写法:

  1. const curry = fn =>
  2. judge = (...args) =>
  3. args.length === fn.length
  4. ? fn(...args)
  5. : (...arg) => judge(...args, ...arg)