一、写在前面
今天了解一个知识点——尾调用,接下来将详细总结一下我对于尾调用的理解。以及为什么要使用尾调用。
二、什么是尾调用
尾调用就是在函数执行的最后一步执行另一个函数。
如下所示为尾调用:
function f(x) {
return g(x)
}
下面的两种情况都不属于尾调用
function f(x){
let y = g(x)
return y
}
function f(x){
return g(x) + 1
}
通过一个例子来看尾调用
function a() {
return b() + 1
}
function b() {
return c() + 2
}
function c() {
return 3
}
console.log(a()) //6
function a() {
return b(1)
}
function b(num) {
return c(num + 2)
}
function c(num) {
return num + 3
}
console.log(a()) //6
上面那种情况不是尾调用,下面的是尾调用。为什么说尾调用的性能要比没有使用尾调用的性能好呢?
我们都知道在A函数调用时会将A函数放入调用栈中,如果在A函数中存在另一个函数B调用,此时也会将B压入调用栈中,等待函数B调用完成后,此时才可以从调用栈中弹出函数A和函数B。那如果使用尾调用时,当执行函数A时,将函数A压入调用栈中,当执行A时发现存在函数B,并且函数B为函数A的最后一步调用,此时可以将函数A从调用栈中弹出,将函数B压入调用栈中。这样下来性能就做到了优化。
三、尾递归
函数调用自身,叫做递归。如果尾调用自身,则就称为尾递归。
递归非常消耗内存,因为需要同时保存成千上万个调用记录,很容易发生栈溢出的错误。但是对于尾递归来说,由于只存在一个调用记录,所以永远不会发生栈溢出。
function factorial(n) {
if (n === 1) return 1
return n * factorial(n - 1)
}
console.log(factorial(5)) //120
如上述代码所示是一个递归调用求阶乘的算法。但是由于递归可能会导致栈溢出。尾递归优化
function factorial(n, total) {
if (n === 1) return total
return factorial(n - 1, total * n)
}
console.log(factorial(5, 1))
// factorial(5, 1)
// factorial(4, 5)
// factorial(3, 20)
// factorial(2, 60)
// factorial(1, 120)
// return total
如上述代码所示,我们使用尾递归,fatorial(5, 1)
的值也就是factorial(4, 5)
的值,同时也是factorial(3, 20)
…这样调用栈中,每一次都只有一个函数,不会导致stack overflow
。
四、对阶乘尾递归的优化可以对其中一个参数使用默认值,当我们没有输入该参数时,则就会使用默认值。
function factorial(n, total = 1) {
if(n === 1) return total
return factorial(n - 1, total * n)
}
console.log(factorial(5))
五、严格模式ES6
中的尾调用优化只在严格模式下开启,正常模式下无效。
这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数。
尾调用优化时,函数的调用栈会被改写,因此上面的两个变量就会失真。严格模式下禁用这两个变量,所以尾调用模式仅在严格模式下生效。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_47450807/article/details/123396329
内容来源于网络,如有侵权,请联系作者删除!