JavaScript 柯里化(Currying)

柯里化

柯里化(Currying)是一种将接受多个参数的函数转换为一系列接受单个参数的函数的技术。

柯里化不会调用函数。它只是对函数进行转换。

简单来说,柯里化是一种函数的转换,原本一个函数需要一次性接收多个参数才能执行,通过柯里化,将其改造成可以逐步接收参数,并在接收完所有必要参数后才执行最终的操作。

比如将一个函数从可调用的 f(a, b) 转换为可调用的 f(a)(b)
创建一个辅助函数 curry(func),该函数将对两个参数的函数 sum 执行柯里化。换句话说,对于两个参数的函数 sum(a, b) 执行 curry(func) 会将其转换为以 sum(a)(b) 形式运行的函数:

// 非柯里化的写法
function sum(a, b) {
  return a + b;
}

// 将函数sum 进行 柯里化 转换
function curry(func) {
  return function(a) {
    return function(b) {
      return func(a, b);
    };
  };
}
// 具体使用
let curriedSum = curry(sum);
console.log(curriedSum(1)(2)); // 3

实现原理:

  1. curry(func) 的结果就是一个包装器 function(a)
  2. 当它被像 curriedSum(1) 这样调用时,它的参数会被保存在词法环境中,然后返回一个新的包装器 function(b)
  3. 然后这个包装器被以 (2) 为参数调用,并且,它将该调用传递给原始的 sum 函数。

柯里化更高级的实现,例如 lodash 库的 _.curry,会返回一个包装器,该包装器允许函数被正常调用或者以部分应用函数(partial)的方式调用:

function sum(a, b) {
  return a + b;
}

let curriedSum = _.curry(sum); // 直接使用来自 lodash 库的 _.curry

// 以常规函数传参的形式被调用
console.log( curriedSum(1, 2) ); // 3

// 以部分应用函数的方式调用
console.log( curriedSum(1)(2) ); // 3

柯里化的优点

  • 延迟计算:可以根据需要逐步传递参数,实现按需计算。
  • 函数复用:通过柯里化,可以创建具有特定初始参数的新函数,提高了函数的复用性。

举例说明延迟计算、函数复用:

function curriedSum(a) {
  return function(b) {
    return a + b
  };
}

// 先不计算,创建一个固定参数为 1 的计算函数
let curriedSumFunc = curriedSum(1);

// 在需要的时候进行计算
console.log(curriedSumFunc(2)); // 3
console.log(curriedSumFunc(5)); // 6

// 还可以创建很多固定参数不一样的计算函数

// 复用,创建一个固定参数为 2 的计算函数
let curriedSumFunc2 = curriedSum(2);

// 复用,创建一个固定参数为 3 的计算函数
let curriedSumFunc3 = curriedSum(3);

curriedSum 是一个柯里化函数。
curriedSum 接受一个参数 a ,然后返回一个新的函数function(b)。这个新函数又接受一个参数 b ,并返回 a + b 的结果。
当执行 let curriedSumFunc = curriedSum(1) 时, a 的值被固定为 1此时,没有进行任何计算,而是返回了一个接受参数 b 的函数,并将其赋值给 curriedSumFunc
当执行 console.log(curriedSumFunc(2)) 时,开始计算,此时 b 的值为 2 ,因为之前 a 被固定为 1 ,所以计算结果为 1 + 2 = 3

  • 参数灵活:更容易处理参数数量不确定或部分参数需要提前固定的情况。
    例如,有一个用于格式化和输出信息的日志函数 log(date, importance, message)。在实际项目中,此类函数具有很多有用的功能,例如通过网络发送日志(log)等:
// 常规写法
function log(date, importance, message) {
  console.log(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

// 将log()柯里化
curriedLog = _.curry(log);

// 柯里化后,正常调用curriedLog(date, importance, message)
curriedLog(new Date(), "DEBUG", "some debug"); 

// curriedLogNow 会是带有固定第一个参数的日志的部分应用函数,固定参数 date
// 换句话说,就是更简短的“部分应用函数(partially applied function)”或“部分函数(partial)”
let curriedLogNow = curriedLog(new Date());   

// 此时,curriedLogNow第一个参数固定,使用curriedLogNow(importance, message)
curriedLogNow("INFO", "message"); // [HH:mm] INFO message

// 更进一步,为当前的调试日志(debug log)提供便捷函数
let debugLog = curriedLogNow('DEBUG');
debugNow("message"); // [HH:mm] DEBUG message

柯里化之后,我们没有丢失任何东西:log 依然可以被正常调用。
可以轻松地生成部分应用函数,例如用于生成今天的日志的部分应用函数curriedLogNow ()

柯里化会使得函数的调用又嵌套了多层,导致代码的可读性降低。

柯里化的高级实现

以下是一个更高级的柯里化实现示例,它可以处理具有多个参数的函数:

// func: 要转换的函数
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));
      }
    }
  };
  
}

在上述实现中,curry 函数接受一个要进行柯里化的函数 func 作为参数。
curried 函数是通过 curry 函数返回的包装器函数。它使用剩余参数 ...args 来接收每次调用时传入的参数。
执行方法时,有2个if分支:

  1. 如果传入的 args 长度与原始函数 func 定义的参数长度相同或更长,就直接使用 func.apply(this, args) 来执行原始函数并返回结果。
  2. 否则,返回一个新的函数function(...args2):该函数在被调用时,它将重新应用 curried,会将新传入的参数 args2 与之前保存的参数 args 合并(通过 args.concat(args2)),然后再次调用 curried 函数,形成递归调用,直到参数数量满足原始函数的要求。

使用示例:

function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = curry(sum);
// 以常规函数传参,正常调用
console.log( curriedSum(1, 2, 3) );  // 6

// 第一个参数柯里化
console.log( curriedSum(1)(2,3) );   // 6

// 全部参数柯里化
console.log( curriedSum(1)(2)(3) );  // 6

注意

  1. 柯里化要求函数具有固定数量的参数。
    使用 rest 参数的函数,例如 f(...args),不能以这种方式进行柯里化。
  2. 根据定义,柯里化应该将 sum(a, b, c) 转换为 sum(a)(b)(c)
    柯里化函数的高级实现 使得函数可以被多参数变体调用。
  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值