概念
首先有一个概念是尾调用,就是指某个函数的最后一步是调用另一个函数,如下
function f(x){
return g(x);
}
而下面几种情况都不属于调用
1.
function f(x){
let y = g(x);
return y;
}
2.
function f(x){
return g(x) + 1;
}
3.
function f(x){
g(x);
//相当于 return undefined
}
顾名思义,尾递归就是在函数尾部进行递归调用。
作用
尾递归本身只是一种coding方式,但加以利用可以对一些递归操作进行优化,避免栈溢出。
比如一些求阶乘的例子:
不使用尾递归
// 无尾递归优化
function f(n) {
if (n === 1)
return 1
return n * f(n - 1)
}
这并不是一个尾递归,因为函数结尾不仅是递归调用函数,而是多了一个n的运算,意味着计算完f(n-1),还需要回来乘上n。导致这一上下文需要被保存,计算n的阶乘,就需要保存n个上下文记录。
使用尾递归
// 第一次调用f时,total设为1
function f(n, total = 1) {
if (n === 1)
return total
return f(n - 1, n * total) // total 结果和 n 相乘作为参数放入到函数中
}
f 尾部进行递归调用,且将当前结果作为参数传入递归中,那么当前的栈帧就不用保存了,编译时实际上是按照循环的方式执行,相当于如下代码:
function f(n){
let total = 1;
for(let i=n;i>0;i--){
total = total * i;
}
return total
}
目前一些主流浏览器并不支持尾递归的优化,因此在coding的使用多用循环代替递归是个不错的选择。