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

阅读时间:约 10 分钟
适合人群: 具备 JavaScript 基础、想深入理解函数式编程与高阶函数实战的前端工程师。

本文目标:

  • 了解如何在没有函数名的情况下实现递归
  • 掌握构建通用递归生成器的思路
  • 学会在真实项目中使用高阶函数递归处理复杂逻辑

从匿名递归开始

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

scss 复制代码
(g => g(g))(f => n => n < 2 ? 1 : n * f(f)(n - 1))(6);

执行结果是:

复制代码
720

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

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

<math xmlns="http://www.w3.org/1998/Math/MathML"> n ! = n × ( n − 1 ) × ( n − 2 ) × ⋯ × 1 n! = n \times (n - 1) \times (n - 2) \times \cdots \times 1 </math>n!=n×(n−1)×(n−2)×⋯×1

拆解原理:

  • (f => n => n < 2 ? 1 : n * f(f)(n - 1)):定义阶乘逻辑;
  • (g => g(g))自应用函数(Self-Application Function)
  • (g => g(g))(f => ...):将函数传入自身,让匿名函数内部实现递归。

这种方式实现了没有名字的递归函数


提取为可复用的函数

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

ini 复制代码
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() 封装了匿名递归结构,传入的函数只需要关心逻辑。


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

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

javascript 复制代码
const createRecursiveFunction = (f) => {
  return (g => g(g))(h => f((...args) => h(h)(...args)));
};

解释:

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

例 1:阶乘(factorial)

ini 复制代码
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)

ini 复制代码
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 阶,问总共有多少种爬法?

ini 复制代码
const climbStairsLogic = (self) => n => {
  return n <= 2 ? n : self(n - 1) + self(n - 2);
};

const climbStairs = createRecursiveFunction(climbStairsLogic);

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

例 4:棋盘麦粒问题(chessGrains)

棋盘第一个格子放 1 颗麦子,每下一个格子是前一个的 2 倍。问第 64 个格子上有多少颗?

ini 复制代码
const chessGrainsLogic = (self) => (n, acc = 1) => {
  return n === 0 ? acc : self(n - 1, acc * 2);
};

const chessGrains = createRecursiveFunction(chessGrainsLogic);

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

完整代码

scss 复制代码
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. 二分查找

好的,我帮你把这一节整理成 掘金文章的新一章,保持和前文一致的 Markdown 风格,标题、代码、说明都完整,方便直接发布。


十、递归练习与思维扩展

在前文中,我们学习了如何用高阶函数和通用递归生成器 createRecursiveFunction 构建各种递归函数。

为了加深理解,可以尝试一些练习题,实践不同类型的递归逻辑。

本章给出三个示例:

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

1. 非尾递归版阶乘

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

ini 复制代码
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. 求数组和

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

ini 复制代码
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. 二分查找

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

ini 复制代码
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 负责自引用;
  • 递归逻辑模板统一:
javascript 复制代码
const logic = (self) => (...args) => {
  // ...递归逻辑
  return self(...args); // 调用自身
};
const fn = createRecursiveFunction(logic);

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


项目中的实际应用场景

在真实项目中,高阶函数递归能让代码更灵活、更模块化。

常见的实战场景包括:

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

遍历树形菜单

ini 复制代码
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));

输出:

bash 复制代码
[
  { id: 1, name: '首页' },
  { id: 2, name: '用户管理' },
  { id: 3, name: '用户列表' },
  { id: 4, name: '用户详情' }
]

异步重试机制

javascript 复制代码
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 <= 0) throw err;
    console.log(`重试中,还剩 ${retries} 次...`);
    await new Promise(r => setTimeout(r, delay));
    return self(fn, retries - 1, delay);
  }
};

const retry = createRecursiveFunction(retryLogic);

let count = 0;
const unstableApi = async () => {
  count++;
  if (count < 3) throw new Error('失败');
  return '成功';
};

retry(unstableApi, 5, 500)
  .then(res => console.log('结果:', res))
  .catch(err => console.error('最终失败:', err.message));

输出:

erlang 复制代码
重试中,还剩 5 次...
重试中,还剩 4 次...
结果: 成功

动态轮询任务

javascript 复制代码
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 <= 0) return false;

  console.log(`未完成,${interval}ms 后再次检查...`);
  await new Promise(r => 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 ? '任务完成!' : '超时未完成');
});

从抽象到实用

场景 应用优势
树结构处理 灵活定义逻辑,便于复用
异步重试 自动化错误恢复
动态轮询 可配置、可扩展

高阶函数递归不仅是理论技巧,更是函数式思维在工程化中的体现。

当逻辑复杂、变化频繁时,用函数组合取代命名函数,可以极大提升灵活性。


结语

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

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

如果你觉得本文对你有帮助,欢迎点赞、收藏或分享,让更多人了解高阶函数的实际应用。

你的点赞,就是我继续写作的最大动力。

相关推荐
IT古董2 小时前
全面理解 Corepack:Node.js 的包管理新时代
前端·node.js·corepack
Jonathan Star2 小时前
NestJS 是基于 Node.js 的渐进式后端框架,核心特点包括 **依赖注入、模块化架构、装饰器驱动、TypeScript 优先、与主流工具集成** 等
开发语言·javascript·node.js
学习3人组2 小时前
清晰地说明 NVM、NPM 和 NRM 在 Node.js 开发过程中的作用
前端·npm·node.js
矢心2 小时前
setTimeout 和 setInterval:看似简单,但你不知道的使用误区
前端·javascript·面试
一枚前端小能手2 小时前
🧭 使用历史记录 API - SPA导航与状态管理的完整指南
前端·javascript
用户47949283569152 小时前
从字符串满天飞到优雅枚举:JavaScript 常量管理的几种姿势
前端·javascript
qq_415216252 小时前
Vue3+vant4+Webpack+yarn项目创建+vant4使用注意明细
前端·webpack·node.js
李建军2 小时前
ASP.NET Core Web 应用SQLite数据连接显示(1)
前端
耀耀切克闹灬3 小时前
word文档转html(mammoth )
前端