一文带你了解柯里化

164 阅读2分钟

定义

柯里化是一种关于函数的高阶技术。

它指的是一种函数的转换,即将一个函数从可调用的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));
      }
    }
  }
}

当我们要运行该函数时,会有以下两个分支:

  1. 如果传入的args长度与原始函数所定义的参数长度即func.length相等或更长,则直接利用apply()g改变this指向,传入参数后调用原始函数;
  2. 否则我们将获得一个偏函数,返回一个包装器,它将之前传入的参数和新参数一起传入后应用curried.

此处再补充一个优雅写法:

const curry = fn =>
   judge = (...args) => 
      args.length === fn.length
         ? fn(...args)
         : (...arg) => judge(...args, ...arg)

如有错误,敬请指正!