函数柯里化

基础知识

  1. 什么是函数柯里化 (Currying)? 柯里化是一种将一个接收多个参数的函数,转变为一系列接收单一参数(或部分参数)的函数的技术。每当调用这些新函数并传入一个参数后,它会返回一个接收下一个参数的新函数,直到所有参数都集齐,最后才会执行原始函数并返回最终结果。

    简单来说,柯里化就是一种“参数的延迟接收”和“函数的预加载”技术。

    // 普通函数
    function add(a, b, c) {
      return a + b + c;
    }
    add(1, 2, 3); // 6
    
    // 柯里化后的函数
    let curriedAdd = curry(add);
    curriedAdd(1)(2)(3); // 6
    
  2. 柯里化的优点

    • 参数复用: 我们可以通过预先提供一部分参数,来创建一个功能更具体、更专业的新函数。
    • 提前确认/延迟执行: 我们可以不断地收集和准备参数,将真正的计算推迟到所有参数都准备就绪时再执行。
    • 提高函数通用性和组合性: 柯里化后的函数粒度更小,更容易进行函数组合(Function Composition)。
  3. 应用场景

    • 创建偏函数 (Partial Application): 例如,创建一个 add5 函数,它总是将传入的数字加 5。
    • 代码组织与复用: 在函数式编程中,柯里化是实现高阶函数和组合思想的基础。
    • 延迟计算: 在需要进行一系列配置,最后才执行的场景中非常有用。

核心思路

我们的目标是创建一个高阶函数 curry(func),它接收一个普通函数 func 作为参数,并返回一个柯里化后的新函数。这个新函数需要具备以下能力:

  1. 参数收集器: 柯里化函数的核心是一个“参数收集器”。它需要一个地方(通常是一个数组)来存放历次调用时传入的参数。

  2. 闭包的应用: curry 函数返回的新函数,必须通过闭包来“记住”两个关键信息:

    • 原始的目标函数 func
    • 已经收集到的参数列表 args
  3. 递归与返回: 返回的柯里化函数在被调用时,需要做出判断:

    • 参数够了吗?: 检查当前收集到的参数数量,是否已经达到了原始函数 func 所需的参数数量(可以通过 func.length 获取)。
    • 如果参数还不够: 将新传入的参数追加到已收集的参数列表中,然后返回一个新的、同样具备柯里化能力的函数。这个新函数会继续这个收集过程。这是一个递归的思路。
    • 如果参数已经足够: 将所有收集到的参数,一次性地传递给原始函数 func 并执行它,然后返回最终的计算结果。这是递归的出口。

代码实现

下面是一个通用性很强的 curry 函数实现。

/**
 * 实现一个通用的函数柯里化函数
 * @param {Function} func - 需要被柯里化的原始函数
 * @returns {Function} - 返回一个柯里化后的新函数
 */
function curry(func) {
  // 1. curry 函数返回一个新的函数(柯里化核心函数)
  //    ...initialArgs 是首次可能传入的预设参数
  return function curried(...initialArgs) {
    // 2. 检查已收集的参数数量是否达到了原始函数所需的数量
    if (initialArgs.length >= func.length) {
      // 3. 如果参数足够,则直接执行原始函数并返回结果
      return func.apply(this, initialArgs);
    } else {
      // 4. 如果参数不够,则返回一个新的函数,继续收集参数
      return function (...nextArgs) {
        // 5. 递归调用 curried,将旧参数和新参数合并后传入
        return curried.apply(this, [...initialArgs, ...nextArgs]);
      };
    }
  };
}

代码解析

  1. 高阶函数结构

    • curry(func) 接收目标函数 func,返回柯里化函数 curried,体现高阶函数特性(函数返回函数)。
    • curried 函数通过 rest 参数 ...initialArgs 收集所有传入参数,支持分步传参。
  2. 参数数量判断

    • 核心条件 if (initialArgs.length >= func.length) 决定是否执行原始函数。
    • func.length 属性表示函数定义时期望的参数个数(不含 rest 参数),例如 function(a, b){}length 为 2。
    • 通过比较已收集参数 initialArgs 的长度与 func.length,判断参数是否足够。
  3. 执行原始函数

    • 当参数足够时,通过 return func.apply(this, initialArgs) 执行原始函数。
    • 使用 apply 确保:
      • 正确绑定 this 上下文。
      • 以数组形式传递所有收集到的参数。
  4. 返回新函数(继续收集)

    • 当参数不足时,返回匿名函数 function(...nextArgs) { ... }
    • 新函数负责接收后续调用传入的参数 nextArgs,继续收集参数直至满足条件。
  5. 递归调用与参数合并

    • 核心逻辑 return curried.apply(this, [...initialArgs, ...nextArgs]) 通过递归实现参数累积。
    • 递归调用 curried 函数自身,通过 apply 合并前后参数:
      • [...initialArgs, ...nextArgs] 将历史参数与新参数合并为新数组。
      • 递归持续至合并参数长度满足 func.length,最终执行原始函数。
    • 此机制实现了分步传参和延迟执行的柯里化特性。

使用示例

// 准备一个多参数函数
function multiply(a, b, c, d) {
  return a * b * c * d;
}

// 柯里化
const curriedMultiply = curry(multiply);

// 各种调用方式
console.log(curriedMultiply(2)(3)(4)(5)); // 输出: 120
console.log(curriedMultiply(2, 3)(4, 5)); // 输出: 120
console.log(curriedMultiply(2, 3, 4)(5)); // 输出: 120

// 参数复用:创建一个“乘以6”的函数
const multiplyBy6 = curriedMultiply(2, 3);
console.log(multiplyBy6(4, 5)); // 输出: 120
console.log(multiplyBy6(10, 10)); // 输出: 600
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""