文章

用高阶函数实现递归:从匿名函数到通用递归生成器

用高阶函数实现递归:从匿名函数到通用递归生成器

阅读时间:约 10 分钟 适合人群: 具备 JavaScript 基础、想深入理解函数式编程与高阶函数实战的前端工程师。 本文目标:> 了解如何在没有函数名的情况下实现递归 掌握构建通用递归生成器的思路 学会在真实项目中使用高阶函数递归处理复杂逻辑> 从匿名递归开始

先看一段看似难以理解的代码:

1
(g => g(g))(f => n => n < 2 ? 1 : n * f(f)(n - 1))(6);

执行结果是:

1
720

6!(6 的阶乘(factorial of 6))。

在数学中,阶乘(factorial) 是指一个正整数与它所有比它小的正整数的乘积:

n!=n×(n−1)×(n−2)×⋯×1n! = n \times (n - 1) \times (n - 2) \times \cdots \times 1n!=n×(n−1)×(n−2)×⋯×1

拆解原理:

  • (f => n => n :定义阶乘逻辑;- (g => g(g)):自应用函数(Self-Application Function) ;- (g => g(g))(f => …):将函数传入自身,让匿名函数内部实现递归。 这种方式实现了没有名字的递归函数。

提取为可复用的函数

为了可读性更高,我们提取一个通用的封装函数:

1
2
3
4
5
6
const fn = (f) => {
  return (g => g(g))(f);
};

const fn1 = fn(f => (n, acc = 1) => n === 0 ? acc : f(f)(n - 1, n * acc));
console.log(fn1(6)); // 720

fn() 封装了匿名递归结构,传入的函数只需要关心逻辑。

构建通用的递归函数生成器

我们进一步封装成更通用的写法:

1
2
3
const createRecursiveFunction = (f) => {
  return (g => g(g))(h => f((...args) => h(h)(...args)));
};

解释:

  • f 是接收 self 参数的函数;- self 表示递归自身;- (g => g(g)) 让函数可以引用自身。

    例 1:阶乘(factorial)

1
2
3
4
5
6
7
const factorialLogic = (self) => (n, acc = 1) => {
  return n === 0 ? acc : self(n - 1, n * acc);
};

const factorial = createRecursiveFunction(factorialLogic);

console.log(factorial(6)); // 720

这是一种尾递归写法,性能更优。

例 2:斐波那契数列(fibonacci)

1
2
3
4
5
6
7
const fibonacciLogic = (self) => (n) => {
  return n <= 1 ? n : self(n - 1) + self(n - 2);
};

const fibonacci = createRecursiveFunction(fibonacciLogic);

console.log(fibonacci(6)); // 8

例 3:爬楼梯问题(climbStairs)

每次可以爬 1 或 2 阶,问总共有多少种爬法? ```javascript const climbStairsLogic = (self) => n => { return n <= 2 ? n : self(n - 1) + self(n - 2); };

const climbStairs = createRecursiveFunction(climbStairsLogic);

console.log(climbStairs(6)); // 13

1
2
3
4
5
6
7
8
9
10
11
12
## 例 4:棋盘麦粒问题(chessGrains)

> 棋盘第一个格子放 1 颗麦子,每下一个格子是前一个的 2 倍。问第 64 个格子上有多少颗?
```javascript
const chessGrainsLogic = (self) => (n, acc = 1) => {
  return n === 0 ? acc : self(n - 1, acc * 2);
};

const chessGrains = createRecursiveFunction(chessGrainsLogic);

console.log(chessGrains(63)); // 9223372036854775808

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const createRecursiveFunction = (f) => {
  return (g => g(g))(h => f((...args) => h(h)(...args)));
};

// 阶乘
const factorialLogic = (self) => (n, acc = 1) =>
  n === 0 ? acc : self(n - 1, n * acc);
const factorial = createRecursiveFunction(factorialLogic);
console.log('factorial(6) =', factorial(6));

// 斐波那契
const fibonacciLogic = (self) => (n) =>
  n <= 1 ? n : self(n - 1) + self(n - 2);
const fibonacci = createRecursiveFunction(fibonacciLogic);
console.log('fibonacci(6) =', fibonacci(6));

// 爬楼梯
const climbStairsLogic = (self) => n =>
  n <= 2 ? n : self(n - 1) + self(n - 2);
const climbStairs = createRecursiveFunction(climbStairsLogic);
console.log('climbStairs(6) =', climbStairs(6));

// 棋盘麦粒
const chessGrainsLogic = (self) => (n, acc = 1) =>
  n === 0 ? acc : self(n - 1, acc * 2);
const chessGrains = createRecursiveFunction(chessGrainsLogic);
console.log('chessGrains(63) =', chessGrains(63));

总结:高阶函数的力量

| 概念 | 含义 || — | — || 高阶函数 | 接收或返回函数的函数 || 匿名递归 | 无需函数名即可递归 || 自应用函数 (g => g(g)) | 实现匿名自引用的关键 || 通用递归生成器 | 可动态生成任意递归逻辑 |

十、递归练习与思维扩展

在前文中,我们学习了如何用高阶函数和通用递归生成器 createRecursiveFunction 构建各种递归函数。 为了加深理解,可以尝试一些练习题,实践不同类型的递归逻辑。

本章给出三个示例:

  1. 非尾递归版阶乘2. 求数组和3. 二分查找

    1. 非尾递归版阶乘

非尾递归的特点是递归调用不是最后一步,返回值还要参与计算。

1
2
3
4
5
6
7
8
const factorialLogic = (self) => (n) => {
  if (n === 0) return 1;
  return n * self(n - 1); // 非尾递归
};

const factorial = createRecursiveFunction(factorialLogic);

console.log(factorial(6)); // 输出 720

解析:> 每次递归返回一个值参与乘法计算; 调用栈会随着 n 增大而增加。>

2. 求数组和

利用递归求数组所有元素的和。

1
2
3
4
5
6
7
8
9
const sumArrayLogic = (self) => (arr) => {
  if (arr.length === 0) return 0;
  const [head, ...tail] = arr;
  return head + self(tail); // 非尾递归
};

const sumArray = createRecursiveFunction(sumArrayLogic);

console.log(sumArray([1, 2, 3, 4, 5])); // 输出 15

解析:> 每次取数组的第一个元素 head,递归计算剩余元素 tail 的和; 递归调用返回值与 head 相加得到最终结果。>

3. 二分查找

在有序数组中高效查找目标元素索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const binarySearchLogic = (self) => (arr, target, left = 0, right = arr.length - 1) => {
  if (left > right) return -1; // 未找到

  const mid = Math.floor((left + right) / 2);
  const value = arr[mid];

  if (value === target) return mid;
  if (value > target) return self(arr, target, left, mid - 1);
  return self(arr, target, mid + 1, right);
};

const binarySearch = createRecursiveFunction(binarySearchLogic);

const arr = [1, 3, 5, 7, 9, 11, 13];
console.log(binarySearch(arr, 7));  // 输出 3
console.log(binarySearch(arr, 4));  // 输出 -1

解析:> 每次递归缩小查找范围; 递归结束条件是 left right; 返回目标元素索引或 -1。>

特征

| 函数 | 类型 | 递归特点 || — | — | — || 阶乘 | 非尾递归 | 调用后还有乘法操作 || 数组求和 | 非尾递归 | 每次取头递尾 || 二分查找 | 非尾递归 | 分治思想,递归缩小范围 |

核心思路

  • 只需提供递归逻辑,createRecursiveFunction 负责自引用;- 递归逻辑模板统一:
    1
    2
    3
    4
    5
    
    const logic = (self) => (...args) => {
    // ...递归逻辑
    return self(...args); // 调用自身
    };
    const fn = createRecursiveFunction(logic);
    

通过练习,可以更好理解高阶函数 + 递归的组合思维,并能应用于实际项目中的树结构、异步任务等场景。

项目中的实际应用场景

在真实项目中,高阶函数递归能让代码更灵活、更模块化。 常见的实战场景包括:

  • 树结构遍历(菜单、评论、权限等)- 异步重试机制(API 调用失败重试)- 动态轮询(等待异步任务完成)

    遍历树形菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const createRecursiveFunction = (f) => {
  return (g => g(g))(h => f((...args) => h(h)(...args)));
};

// 扁平化树
const flattenTreeLogic = (self) => (nodes, result = []) => {
  for (const node of nodes) {
    result.push({ id: node.id, name: node.name });
    if (node.children?.length) self(node.children, result);
  }
  return result;
};

const flattenTree = createRecursiveFunction(flattenTreeLogic);

const menu = [
  { id: 1, name: '首页' },
  {
    id: 2,
    name: '用户管理',
    children: [
      { id: 3, name: '用户列表' },
      { id: 4, name: '用户详情' },
    ],
  },
];

console.log(flattenTree(menu));

输出:

1
2
3
4
5
6
[
  { id: 1, name: '首页' },
  { id: 2, name: '用户管理' },
  { id: 3, name: '用户列表' },
  { id: 4, name: '用户详情' }
]

异步重试机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const createRecursiveFunction = (f) => {
  return (g => g(g))(h => f((...args) => h(h)(...args)));
};

const retryLogic = (self) => async (fn, retries = 3, delay = 1000) => {
  try {
    return await fn();
  } catch (err) {
    if (retries  setTimeout(r, delay));
    return self(fn, retries - 1, delay);
  }
};

const retry = createRecursiveFunction(retryLogic);

let count = 0;
const unstableApi = async () => {
  count++;
  if (count  console.log('结果:', res))
  .catch(err => console.error('最终失败:', err.message));

输出:

1
2
3
重试中,还剩 5 次...
重试中,还剩 4 次...
结果: 成功

动态轮询任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const createRecursiveFunction = (f) => {
  return (g => g(g))(h => f((...args) => h(h)(...args)));
};

const pollingLogic = (self) => async (checkFn, interval = 1000, maxTries = 10) => {
  const done = await checkFn();
  if (done) return true;
  if (maxTries  setTimeout(r, interval));
  return self(checkFn, interval, maxTries - 1);
};

const polling = createRecursiveFunction(pollingLogic);

let progress = 0;
const checkTask = async () => {
  progress += 30;
  return progress >= 100;
};

polling(checkTask, 1000, 5).then(done => {
  console.log(done ? '任务完成!' : '超时未完成');
});

从抽象到实用

| 场景 | 应用优势 || — | — || 树结构处理 | 灵活定义逻辑,便于复用 || 异步重试 | 自动化错误恢复 || 动态轮询 | 可配置、可扩展 | 高阶函数递归不仅是理论技巧,更是函数式思维在工程化中的体现。 当逻辑复杂、变化频繁时,用函数组合取代命名函数,可以极大提升灵活性。

结语

本文从匿名递归出发,一步步构建了一个通用的递归生成器,并结合项目实战展示了高阶函数递归的实用价值。

这类模式的核心思想是: 将递归结构与逻辑解耦,让函数更通用、更可组合。

如果你觉得本文对你有帮助,欢迎点赞、收藏或分享,让更多人了解高阶函数的实际应用。 你的点赞,就是我继续写作的最大动力。

本文由作者按照 CC BY 4.0 进行授权