函数表达式实现递归的关键是让函数能引用自身,命名函数表达式(如function fact(n) {...})最稳妥,或用高阶函数如recur封装自引用。

函数表达式实现递归,关键不是“能不能调自己”,而是“怎么让函数知道自己是谁”。因为函数表达式默认是匿名的,直接在内部写函数名会报错(比如 const f = () => f() 在严格模式下会提示 f is not defined)。所以必须主动给它一个“名字”或“引用”,才能安全递归。
用命名函数表达式绑定名称
最稳妥、最清晰的方式:给函数表达式起个名字,这个名字只在函数体内可见,但足以支持递归调用。
- 写法:
const factorial = function fact(n) { return n - 说明:
fact是函数的内部标识名,外部仍通过factorial调用;fact在函数体内部可安全使用,不会污染作用域 - 优势:语义明确、调试友好(堆栈中显示函数名为
fact)、兼容所有环境
用参数传递自身引用(Y 组合子思路)
适用于需要完全匿名、或动态生成递归逻辑的场景,本质是把函数当作参数传给自己。
- 写法:
const factorial = (f => n => n n => n - 更实用的简化版(带缓存):
const recur = (fn) => (...args) => fn(fn, ...args);,然后const fib = recur((self, n) => n - 注意:可读性较低,一般只在高阶抽象或函数式库中出现,日常开发不推荐
用变量提前声明 + 函数声明替代(简单可靠)
如果只是想快速实现且不强求“纯表达式”,直接改用函数声明是最省心的选择。
- 写法:
function factorial(n) { return n - 说明:函数声明会被提升,名字天然可访问;虽然不是“表达式”,但语义和行为完全一致,且无任何兼容性风险
- 建议:除非明确要求必须用表达式(如箭头函数、立即执行、赋值到对象属性),否则优先选函数声明
避免常见陷阱
这些写法看似简洁,实际有隐患:
-
不要依赖
arguments.callee:已废弃,严格模式下报错,现代 JS 禁用 -
不要用箭头函数直接递归:箭头函数没自己的
this和arguments,更没有名字,const f = n => n ? n * f(n-1) : 1会报f is not defined -
别漏掉终止条件:无论哪种形式,
if (n 这类基线判断必须存在,否则立刻栈溢出











