实现 compose 方法

基础知识

  1. 什么是函数组合 (Function Composition)? 函数组合是一种将多个简单的、单一职责的函数,合并成一个更复杂的新函数的技术。其核心思想是,将一个函数的输出作为另一个函数的输入

    在数学上,如果我们有两个函数 f(x)g(x),那么它们的组合函数 (f ∘ g)(x) 就等同于 f(g(x))

  2. 执行顺序:从右到左 这是 compose 函数的一个关键约定。当我们写 compose(f, g, h) 时,它生成的函数在执行时,函数的调用顺序是从右到左的。也就是说,它等价于 (...args) => f(g(h(...args)))

    • 数据首先被传入最右边的函数 h
    • h 的执行结果被传入中间的函数 g
    • g 的执行结果被传入最左边的函数 f
    • f 的最终结果是整个组合函数的返回结果。

    注意:也有一种从左到右执行的组合方式,通常被称为 pipecomposepipe 功能相同,只是执行顺序相反。

  3. 为什么使用 compose?

    • 提高可读性: 它能让我们避免丑陋的函数嵌套地狱。对比一下:

      // 嵌套方式
      let result = fn1(fn2(fn3(fn4(value))));
      
      // compose 方式
      const composedFn = compose(fn1, fn2, fn3, fn4);
      let result = composedFn(value);
      

      compose 的方式清晰地描述了一个从右到左的数据处理“管道”,更具声明性。

    • 促进函数复用: 它鼓励开发者编写小而美的、功能单一的“纯函数”,这些函数可以像乐高积木一样,在不同的组合中被复用。

核心思路

我们的目标是创建一个高阶函数 compose(...funcs),它接收任意数量的函数作为参数,并返回一个组合后的新函数。

  1. 收集函数: 使用 rest 参数 ...funcs 将所有传入的函数收集到一个数组中。

  2. 处理边界情况:

    • 如果没有传入任何函数,compose() 应该返回一个“恒等函数”,即一个接收什么就返回什么的函数 (arg => arg)。
    • 如果只传入一个函数,compose(fn) 直接返回该函数 fn 即可。
  3. 创建函数链条: 核心思路是利用数组的 reduce 方法,将函数数组“折叠”成一个单一的函数。

    • 我们可以将 [f, g, h] 这个数组,通过 reduce 变为 (...args) => f(g(h(...args)))
    • reduce 的初始累加值是第一个函数 f
    • 在第一次迭代中,afbg。我们返回一个新函数 (...args) => a(b(...args)),即 (...args) => f(g(...args))
    • 在第二次迭代中,a 是上一步返回的新函数,bh。我们再次返回 (...args) => a(b(...args)),它展开后就是 (...args) => ( (...innerArgs) => f(g(...innerArgs)) )(h(...args)),最终效果等同于 (...args) => f(g(h(...args)))

代码实现

下面是一个非常简洁且强大的 compose 函数实现。

/**
 * 函数组合,将多个函数组合成一个新函数,从右到左执行。
 * @param  {...Function} funcs - 需要组合的函数列表
 * @returns {Function} - 返回一个组合后的新函数
 */
function compose(...funcs) {
  // 1. 处理没有传入函数的边界情况
  if (funcs.length === 0) {
    // 返回一个恒等函数
    return (arg) => arg;
  }

  // 2. 处理只传入一个函数的边界情况
  if (funcs.length === 1) {
    return funcs[0];
  }

  // 3. 使用 Array.prototype.reduce 将函数串联起来
  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  );
}

代码解析

  1. 边界情况处理

    • 空函数数组:当 funcs.length === 0 时,返回恒等函数 (arg) => arg。该函数接收任意参数并直接返回,确保无函数组合时调用安全(如避免 TypeError)。
    • 单个函数:当 funcs.length === 1 时,直接返回唯一的函数,无需额外处理,符合“组合单个函数即自身”的逻辑。
  2. 核心实现:数组 reduce 方法

    • reduce 作用:通过 funcs.reduce(...) 从左到右遍历函数数组,逐步组合函数。
    • 累加器逻辑:每次迭代返回新函数作为下一次的累加器,实现函数嵌套调用。
    • 参数说明
      • a:已组合好的函数(累加器)。
      • b:当前处理的函数(数组元素)。
  3. 函数组合逻辑分解

    • 迭代函数定义(a, b) => (...args) => a(b(...args))
      • 外层 (...args) 接收最终调用时的参数。
      • 内层先执行 b(...args)(当前函数处理参数),再将结果传入 a(已组合函数)。
    • 手动模拟 compose(f, g, h)
      • 第一次迭代
        • a = fb = g
        • 返回函数:(...args) => f(g(...args))
      • 第二次迭代
        • a = (...args) => f(g(...args))b = h
        • 返回函数:(...args) => [f(g(...args))](h(...args)),等价于 f(g(h(...args)))
    • 最终结果:reduce 完成后返回嵌套所有函数的调用链,参数从右至左传递(如 compose(f, g, h)(x) 等价于 f(g(h(x))))。
  4. 执行顺序关键点

    • 组合函数调用时,参数先传入最右侧函数(如 h),结果依次传递给左侧函数(gf),形成“右到左”的执行链。
    • reduce 的累加器机制确保每次迭代都将新函数“包裹”在已组合函数外层,最终形成完整的函数调用嵌套。

使用示例

// 准备一些单一功能的函数
const toUpperCase = (str) => str.toUpperCase();
const addExclamation = (str) => `${str}!`;
const reverse = (str) => str.split('').reverse().join('');

// 使用 compose 将它们组合起来
// 执行顺序:reverse -> addExclamation -> toUpperCase
const composedFn = compose(toUpperCase, addExclamation, reverse);

const result = composedFn('hello');

console.log(result); // 输出: "!OLLEH"

const resultShouldBe = toUpperCase(addExclamation(reverse('hello')));
console.log(resultShouldBe); // OLLEH!
// 啊,是我最开始写的示例输出错了,现在更正。
console.log('实际结果:', result); // 应该是 OLLEH!
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 ""