Skip to content

什么是尾递归? ​

尾调用是函数在返回前做的最后一件事就是调用另一个函数,而且不再使用这次调用的结果做计算。尾递归是尾调用自己。严格模式下,引擎可以把这种调用复用成同一个栈帧,避免栈溢出。

原理 ​

阶乘如果写成 return n * factorial(n - 1),乘法发生在递归返回之后,不是尾调用。改成把累乘放进参数:

js
function factorial(n, acc = 1) {
  "use strict"
  if (n <= 1) return acc
  return factorial(n - 1, n * acc)
}

最后一行只有递归调用,当前帧上的 n 和 acc 不再被需要。满足条件时,栈深度可以保持常数。

实际引擎支持不一致。需要稳定的深度时,不要依赖尾调用优化,改成循环,或者自己用栈模拟。递归的价值是树、目录这种结构写起来直接,终止条件必须写在递归调用之前。

追问 ​

尾调用优化需要哪些条件?

严格模式、调用是返回语句里的最后动作、不能把调用包在别的表达式里。闭包如果还要引用当前帧的变量,这一帧就不能丢掉。

递归和循环怎么选?

数据是树、图、嵌套评论,递归更清晰,同时要限制深度或改成显式栈。扁平列表求和用循环或 reduce。

易错点 ​

  • return n * f(n - 1) 看起来在最后,仍然不是尾调用。
  • 非严格模式没有这套优化。
  • 没有终止条件的递归会撑满执行栈,和尾不尾无关。

相关 ​