news 2026/10/3 2:29:53

千古前端图文教程:JavaScript 递归函数完全指南——概念、递归要素与三大经典案例(阶乘 / 喇叭花数 / 斐波那契)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
千古前端图文教程:JavaScript 递归函数完全指南——概念、递归要素与三大经典案例(阶乘 / 喇叭花数 / 斐波那契)
  • 文档
  • 教程
  • 前端

【免费下载链接】Web

千古前端图文教程,超详细的前端入门到进阶知识库。从零开始学前端,做一名精致优雅的前端工程师。

项目地址:https://gitcode.com/gh_mirrors/we/Web
点击查看免费下载

导读

递归(Recursion)是 JavaScript 乃至整个数据结构和算法领域的核心思维:如果一个函数在内部调用这个函数自身,这个函数就是递归函数。递归能够把很多复杂的数据模型拆解为简单问题进行求解,是每一位前端工程师必须掌握的基本功。本篇教程以《04-JavaScript基础/21-递归函数.md》为主线,完整讲解递归的概念、两大要素(递归体与递归出口),并配套阶乘、喇叭花数、斐波那契数列三大实战案例,最后结合仓库中深拷贝实现、arguments.callee 等源码级佐证,讲透递归在前端真实项目中的落地方式。读完本篇,你将能独立写出正确、安全、可读的递归函数,并理解递归与循环、深拷贝之间的联系。

一、什么是递归函数

概念

递归函数是指在函数体内部调用函数自身的函数。用一句最简单的话概括:

如果一个函数在内部调用这个函数自身,这个函数就是递归函数。

递归在数据结构和算法中经常用到,可以将很多复杂的数据模型拆解为简单问题进行求解,因此一定要掌握。典型应用场景包括:树的遍历(DOM 树、组件树)、深拷贝、目录遍历、排序算法(快排、归并)、斐波那契数列、阶乘计算等。

一个最简单的递归示例:

function countDown(n) { if (n === 0) { console.log('发射!'); return; // 递归出口:不再调用自身 } console.log(n); countDown(n - 1); // 递归调用:调用函数自身 } countDown(5);

递归的两大要素

要写出一个正确的递归函数,必须同时具备两个要素:

  • 递归模式(递归体):把大问题拆解为小问题进行分析。即:找到原问题与规模更小的子问题之间的递推关系,在函数体内通过调用自身来完成"分解——求解——合并"的过程。
  • 边界条件(递归出口):确定递归到何时结束。即:当问题规模小到可以直接求解时,不再调用自身,直接返回结果。缺少边界条件的递归会无限调用自身,最终导致调用栈溢出(Stack Overflow)。

可以把递归理解成"俄罗斯套娃":每一层套娃都在重复同样的动作,但规模逐层变小,直到最小的那一层可以直接处理(边界条件),然后逐层返回结果。

二、代码演示:计算阶乘

提问:求一个正整数的阶乘。

阶乘的定义:n! = 1 × 2 × 3 × ... × n,例如5! = 5 × 4 × 3 × 2 × 1 = 120。

普通写法(循环)

在没有学习递归之前,我们通常用循环累乘来实现:

// 函数:计算一个正整数的阶乘 function factorial(n) { let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

递归写法

学习了递归之后,阶乘可以写出更简洁、更贴合数学定义的写法:

// 递归函数:计算一个正整数的阶乘 function factorial(n) { // 递归出口:如果计算1的阶乘,就不用递归了 if (n == 1) return 1; // 开始递归:如果当前这个 n 不是1,就返回 n * (n-1)! return n * factorial(n - 1); } console.log(factorial(5)); // 120

执行过程拆解:factorial(5)需要5 * factorial(4),factorial(4)需要4 * factorial(3)……直到factorial(1)直接返回 1,然后逐层回溯相乘:1 * 2 = 2,2 * 3 = 6,6 * 4 = 24,24 * 5 = 120。整个过程与阶乘的数学递推式n! = n × (n - 1)!完全对应,可见递归写法的本质是用代码直接表达数学递推关系。

对比两种写法:循环写法依赖"计数器 + 累乘变量",递归写法依赖"函数自身调用 + 出口判断"。两者都能正确求解,但递归写法更贴近问题本身的数学结构,可读性更强;代价是多了一次次的函数调用开销。对于阶乘这类单层线性递推,两者效率相当;但在树形、分治类问题(如深拷贝、目录遍历)中,递归往往是唯一自然的解法。

三、案例一:寻找所有的喇叭花数

题目:喇叭花数是一个三位数,其每一位数字的阶乘之和恰好等于它本身,即abc = a! + b! + c!,其中abc表示一个三位数。请找出所有的喇叭花数。

思路:将计算某个数字的阶乘封装成递归函数factorial,然后穷举遍历 100~999 的所有三位数,逐位取出百位、十位、个位,代入条件判断。

代码实现:

// 递归函数:计算一个数的阶乘 function factorial(n) { // 递归出口:如果计算1的阶乘,就不用递归了 if (n == 1) return 1; // 开始递归:如果当前这个 n 不是1,就返回 n * (n-1)! return n * factorial(n - 1); } // 穷举法,从100到999遍历,寻找喇叭花数 for (let i = 100; i <= 999; i++) { // 将数字i转为字符串 const i_str = i.toString(); // abc分别表示百位、十位、个位 const a = Number(i_str[0]); const b = Number(i_str[1]); const c = Number(i_str[2]); // 根据喇叭花数的条件进行判断 if (factorial(a) + factorial(b) + factorial(c) == i) { console.log(i); } }

打印结果:

145

验证:1! + 4! + 5! = 1 + 24 + 120 = 145,正好等于它本身,因此 145 是唯一的喇叭花数。

这个案例展示了递归的典型配合方式:递归函数负责求解子问题(某一位数字的阶乘),外层循环负责穷举与组合。这也是递归最常见的工程形态——递归不孤立存在,而是与遍历、穷举、分治等策略协同工作。

四、案例二:斐波那契数列

斐波那契数列是这样一个数列:1、1、2、3、5、8、13、21、34……,最早是由意大利数学家斐波那契开始研究的。它的规律是:下标为 0 和 1 的项,值为 1;从下标为 2 的项开始,每一项等于前面两项之和。

提问:请找出斐波那契数列的前 10 项。

代码实现:

// 递归函数:返回斐波那契数列中下标为n的那一项的值 function fib(n) { // 下标为0和1的项,值为1 if (n == 0 || n == 1) return 1; // 从下标为2的项开始,每一项等于前面两项之和 return fib(n - 1) + fib(n - 2); } // 循环语句:打印斐波那契数列的前10项 for (let i = 0; i < 10; i++) { console.log(fib(i)); }

打印结果:

1 1 2 3 5 8 13 21 34 55

(原文档代码中循环取i < 15打印了前 15 项,若要严格打印前 10 项可改用i < 10,两种写法输出序列一致、均可运行。)

递归关系分析:fib(n)把问题拆解为两个子问题fib(n - 1)和fib(n - 2),递归出口是n == 0 || n == 1时直接返回 1。这里的递归体是一条分叉递推式(一次调用会派生出两次子调用),比阶乘的单链递归复杂,调用路径呈树状展开。

知识延伸:递归的性能局限。以fib为例,fib(n)会重复计算大量相同的子问题(例如fib(4)会被fib(6)和fib(5)分别计算),导致指数级的时间开销。工程实践中常配合记忆化(Memoization)——用一个缓存对象保存已算过的fib(n)结果,或直接用循环迭代、动态规划来改写。这是从"学会递归"走向"用好递归"的关键一步。

五、小结:递归的更多应用场景

关于递归的案例,今后我们还会学习更多的应用场景。比如深拷贝就会用到递归。

这一点在仓库的源码文档中有直接印证:

  • 在 04-JavaScript基础/30-浅拷贝和深拷贝.md 中明确指出"深拷贝其实就是将浅拷贝进行递归",并给出了for in递归实现:遍历对象的每个属性,若属性值是数组则newObj[key] = []并递归拷贝,若是对象则newObj[key] = {}并递归拷贝,若是基本类型则直接赋值:
// 方法:深拷贝 function deepCopy(newObj, oldObj) { for (let key in oldObj) { // 获取属性值 oldObj[key] let item = oldObj[key]; // 判断这个值是否是数组 if (item instanceof Array) { newObj[key] = []; deepCopy(newObj[key], item); } else if (item instanceof Object) { // 判断这个值是否是对象 newObj[key] = {}; deepCopy(newObj[key], item); } else { // 简单数据类型,直接赋值 newObj[key] = item; } } }
  • 在 07-JavaScript进阶/02-浅拷贝和深拷贝.md 中,还提供了另一种手写递归的deepClone写法,先判断值类型还是引用类型(typeof obj !== 'object' || obj == null时直接返回,这就是递归出口),再判断数组或对象,随后用result[key] = deepClone(obj[key])递归拷贝每一个属性。

深拷贝的递归与阶乘、斐波那契如出一辙:每遇到一个嵌套的复合类型,就调用自身把"拷贝"动作应用到下一层,直到所有值都变成可直接赋值的基本类型——这正是"递归模式 + 边界条件"两大要素的又一次实战体现。

六、仓库源码佐证:递归在函数与 Node 生态中的更多落点

arguments.callee 与递归

在 04-JavaScript基础/20-函数简介.md 中,仓库讲解了arguments.callee的用法:arguments里边的callee属性对应当前正在执行的函数对象。文档特别指出:

在使用函数递归调用时,推荐使用 arguments.callee 代替函数名本身。

示例:

function fun() { console.log(arguments.callee == fun); // 打印结果为true } fun('hello');

也就是说,当函数需要递归调用自身、但又希望函数体与函数名解耦时,可以使用arguments.callee引用当前函数。不过需要说明的是,在 ES5 严格模式下arguments.callee已被禁止,现代开发中更推荐使用具名函数表达式或直接调用函数名。

递归参数在 Node.js 内置模块中的体现

递归思想不仅存在于算法层面,也直接渗透到 Node.js 的 API 设计中。在 11-Node.js/05-Node.js内置模块:fs文件模块.md 中,fs.mkdir的options参数就包含:

  • recursive:是否以递归的方式创建目录,默认为false;
  • mode:设置目录权限,默认为0777。

当recursive: true时,Node.js 底层会递归创建路径中的每一级目录——这正是"递归把复杂问题(创建多层目录)拆解为简单问题(逐级创建单层目录)"的又一个真实工程案例。

七、总结

要点内容
递归概念函数体内部调用函数自身
递归模式(递归体)把大问题拆解为小问题,找到递推关系
边界条件(递归出口)确定递归何时结束,防止无限递归/栈溢出
经典案例阶乘(单链递归)、斐波那契(分叉递归)、喇叭花数(递归 + 穷举)
工程落地深拷贝(递归遍历嵌套对象/数组)、目录递归创建、树形数据遍历
性能提醒注意重复子问题,可配合记忆化或循环迭代优化

掌握了"递归模式 + 边界条件"两要素,再配合本仓库中深拷贝、浅拷贝与深拷贝进阶、函数与 arguments 专题 等配套文档继续深入,你就能把递归这一核心武器熟练运用于前端开发的方方面面。

  • 文档
  • 教程
  • 前端

【免费下载链接】Web

千古前端图文教程,超详细的前端入门到进阶知识库。从零开始学前端,做一名精致优雅的前端工程师。

项目地址:https://gitcode.com/gh_mirrors/we/Web
点击查看免费下载

相关推荐

上一篇:Ship of Harkinian(SoH)手柄兼容性核心:SDL GameControllerDB 映射机制与 `gamecontrollerdb.txt` 全解析
下一篇:Node.js v12.1.0 发布说明深度解析:ICU 64.2 令和年号支持与 nodejs.org 发布文档流水线

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 2:28:20

asc-devkit API UT 覆盖率扫描实战:从接口清单提取到跨架构缺失报告

人工智能深度学习算子库CANNAscend 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言&#xff0c;原生支持C和C标准规范&#xff0c;主要由类库和语言扩展层构成&#xff0c;提供多层级API&#xff0c;满足多维场景算子开发诉求。 项目地址&…

作者头像 李华