递归必须有明确终止条件,否则导致栈溢出或逻辑错误;正确写法需包含基础情形(如 n≤0 时 return)和递归情形(如 countdown(n-1))。

递归不是“自己调自己”就完事了,JavaScript 中写错递归函数,轻则栈溢出报错 RangeError: Maximum call stack size exceeded,重则逻辑死循环、返回 undefined、结果错得离谱。
递归必须有明确的终止条件
没有 return 退出的递归,就是无限调用自己。JS 引擎会不断压栈,直到超出调用栈限制。
常见错误写法:
function countdown(n) {
console.log(n);
countdown(n - 1); // ❌ 没有 if 判断,永远不 return
}正确写法必须包含基础情形(base case)和递归情形(recursive case):
立即学习“Java免费学习笔记(深入)”;
- 基础情形:
n 时直接return(不继续调用) - 递归情形:只在满足条件时调用自身,并确保参数向基础情形靠近
function countdown(n) {
if (n <= 0) return; // ✅ 终止条件
console.log(n);
countdown(n - 1); // ✅ 参数变小,终将触达终止条件
}递归函数必须有合理的返回值传递
很多初学者以为只要“调用了自己”就是递归,但忽略了函数返回值是否被真正使用。比如计算阶乘时漏掉 return,会导致最终结果是 undefined。
错误示例(看似有终止条件,但没传回结果):
function factorial(n) {
if (n === 1) return 1;
factorial(n - 1) * n; // ❌ 缺少 return,上层调用拿到的是 undefined
}正确写法要让每一层都把计算结果返回给上一层:
function factorial(n) {
if (n <= 1) return 1; // ✅ 基础情形返回具体数值
return factorial(n - 1) * n; // ✅ 必须 return 表达式结果
}注意:return 不仅控制流程退出,更承担着“把子问题解传递回去”的职责。
避免隐式递归与副作用干扰
有些函数看似没直接调自己,却因回调、事件或异步操作间接触发重复执行,造成逻辑混乱。例如在 setTimeout 或 addEventListener 里反复绑定同一递归逻辑。
典型陷阱:
- 在递归函数内重复添加事件监听器,导致每次调用都多绑一次
- 用
setInterval模拟递归,但没清理定时器,内存泄漏+调用爆炸 - 参数是引用类型(如数组、对象),递归中意外修改原数据,影响上层判断
建议:优先使用纯函数风格——输入确定、无副作用、返回值唯一。必要时用 JSON.parse(JSON.stringify(obj)) 浅拷贝参数,或用展开语法 [...arr] 隔离状态。
递归最易被忽略的点,其实是参数演化路径是否单调收敛。哪怕写了 if (n > 0),如果递归调用写成 fn(n + 1),照样死循环。别只盯着“有没有 return”,更要盯住“每次调用后,离终点是更近,还是更远”。










