1、尾调用概念:尾调用指的是函数作为另外一个函数的最后一条语句被调用。
示例代码:
function demo(){
return demoAnother();//尾调用
}
2、ES5引擎环境中,尾调用底层实现机制:创建一个新的栈帧,将其推入调用栈来表示函数调用。也就是说,在循环调用中,每一个未用完的栈帧都会保存在内存中,当调用栈变得过大时会造成程序问题。
3、ES6引擎环境中,尾调用底层实现机制:ES6缩减了严格模式下尾调用栈的大小,当满足以下条件时,尾调用不再创建新的栈帧,而是清除并重用当前栈帧:
a、尾调用不访问当前栈帧的变量(就是说函数不是一个闭包)
b、在函数内部,尾调用是最后一条语句
c、尾调用的结果作为函数值返回
满足条件的示例代码:
function demo(){
return demoAnother()
}
不满足条件的示例代码:
a、不返回最终结果
function demo(){
demoAnother();
}
b、尾调用返回后执行其它操作
function demo(){
return 1+demoAnother();
}
尾调用的主要应用场景是递归函数:
function factorial(n){
if(n<=1){
return 1;
}else{
//这个常见的阶乘函数无法实现尾优化,因为其在返回后执行乘法操作
return n*factorial(n-1);
}
}
以上代码当n的数值非常大时,调用栈将不断增长,从而导致栈溢出。
让我们来优化下这个尾调用:
function factorial(n,p=1){
if(n<=1){
return 1*p;
}else{
let result=n*p;
//优化后
return factorial(n-1,result);
}
}
在这个函数中,第二个参数p的默认值为1,我们用他来保存乘法结果,下一次迭代中可以取出它用于计算,不再需要额外的函数调用。当n>1时,先执行一轮乘法计算,然后将结果传递给第二次factorial()调用的参数。
下面解释下为什么这样满足了尾调用优化条件的第一个条件:
有的同学说return 语句使用了result变量,这是一个闭包。请注意,我们使用的是let声明而不是var,使用let声明的变量只在当前代码块有用,当出了当前代码块,会被js引擎的垃圾回收机制回收