尾部调用优化时候遇到的问题

看有个大西瓜
  • 461

在看阮一峰老师的es6,在尾部调用优化那一节,我对文章中提到的

我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

这两段话并不能很好的理解,有哪位大神能解释解释

回复
阅读 1.3k
3 个回答

talk is weak

A:

function fomatSum(val){
    return 'sum / 2 =' + val/2 ;
}
function comp(x,y){
    var sum = x + y;
    return fomatSum(sum);
}
comp(2,4);

B:

function fomatSum(val){
    return 'sum / 2 =' + val/2 ;
}
function comp(x,y){
    var sum = x + y;
    var result = fomatSum(sum);
    return result;
}    

comp(2,4);

A、B两种写法都能得到相同的结果:sum / 2 = 3

在A中,当程序运行到return fomatSum的时候,此时comp函数已经做完了它该做的事情,只需要呼叫comp做接下来的事情。相当于接力赛,comp把接力棒(传入所需参数)给fomatSum后,comp就可以休息了(释放掉),故不需要comp的调用帧。
在B中,当程序运行到fomatSum函数的时候,就跑去执行fomatSum了,但是需要返回值给result(这时候就需要通过调用帧 回到comp函数中),然后return这个值。相当于餐馆点餐,服务员comp把菜单(传入所需参数)给厨师fomatSum后,comp还需要等待fomatSum把东西做好,再给顾客上餐,所以需要调用帧去找到服务员comp。

关于尾调用以及尾递归,建议配合 上下文 进行理解其目的和意义,这里有我之前写的一篇文章 从async await 报错Unexpected identifier 谈谈对上下文的理解 ,可以只看结尾对上下文的分析,希望有帮助。

举个例子,函数递归像是俄罗斯套娃,大的套小的,尾函数就是在一个函数最后一个调用的函数,这个时候就不用去管本作用域下的其他东西,只需要将这个函数返回,也就是不需要保留大号套娃,直接将小号套娃返回,以此下去,只需要一个套娃即可,也就是只保留一个函数帧。

“调用帧”(call frame)就是遗留事项,程序还有事情没做完,就去干别的事了。
尾调用,就是递归时,在程序的最后一步调用函数自身,其他事情都办完了,没有遗留事项
所以就没必要保留旧的“调用帧”,直接用新的“调用帧”覆盖它,这样不会增加内存占用量,也就没有栈溢出了。

你知道吗?

宣传栏