尾调用就是指在调用的函数中,函数的最后一步是调用另一个函数。
function f(x){
return g(x);
}
像上面的例子中,f函数最后调用的g函数,这就是尾调用。
最后一步一定要是return一个函数而且仅能是一个函数,不能是一个表达式,也不能是一个对象。
为什么会有尾调用这个东西,尾调用有什么好处呢?其实在函数的调用过程中,会在内存中形成一个调用记录,称为调用帧,在函数的层层调用时,调用帧会一层层的叠加,这样就形成了调用栈,只有上一层的函数调用完成才会返回上一层,继续执行。
如果进行尾调用优化,在最后一步调用函数后,改函数就可以销毁,相当于直接调用了最终函数
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
上面的例子中函数的执行性能一样,很明显我们感受到,尾调用的使用,使代码的执行更加的快捷。
如果递归函数是尾调用自身,则称为尾递归。
在编码过程中,递归函数无疑是很消耗内存的,稍有不慎可能就会导致栈溢出的问题,但是如果使用尾调用优化递归,就不会有这个问题,这是因为使用尾调用优化的话,不管什么时候都只有一个调用帧,当然也就不会存在栈溢出的情况了。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
上面的例子中是n的阶乘,函数复杂度是O(n)。
如果是尾递归
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
我们发现复杂度变为了O(1)。
尾调用的优化只有严格模式下才有用,如果正常模式下,其实在写递归的时候,我们应该是不到万不得已不使用递归的,有时候可以尽量想法变通使用循环来实现。