这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战
前言
今天复习ES6查缺补漏,看见了尾调用优化这个部分,总结了一波,今天和大伙儿聊聊。
尾调用优化的条件
尾调用优化的条件就是确定外部栈帧真的没有必要存在了。涉及的条件如下: 代码在严格模式下执行;
- 外部函数的返回值是对尾调用函数的调用;
- 尾调用函数返回后不需要执行额外的逻辑;
- 尾调用函数不是引用外部函数作用域中自由变量的团包。
- 下面展示了几个违反上述条件的函数,因此都不符号尾调用优化的要求:
" use strict "; //无优化:尾调用没有返回\
function outerFunction (){
innerFunction ();
)}
//无优化:尾调用没有直接返回
function outerFunction (){
let innerFunctionRe В ult = innerFunction ();
return innerFunctionReSult ;
}
//无优化:尾调用返回后必须转型为宇符串
function outerFunction (){
return innerFunction (). toString ();
}
//无优化:尾调用是一个闭包
function outerFunction (){
1et foo =' bar ';
function innerFunction (){ return foo ;}
return innerFunction ();}
下面是几个符合尾调用优化条件的例子:
" use strict ";
//有优化:栈帧销毁前执行参数计算
function outerFunction ( a , b ){
return innerFunction ( a + b );
}
//有优化:初始返回值不涉及栈帧
function outerFunction ( a , b )(
1f( a < b ){
return a ; 婚入鞭会黥翠 I 眼娘用
return innerFunction ( a + b );
}
//有优化:两个内部函数都在尾部
function OuterFunction ( condition ){
return condition ? innerFunctionA (): innerFunctionB ();
}
差异化尾调用和递归尾调用是容易让人混淆的地方。无论是递归尾调用还是非递归尾调用,都可以 应用优化。引擎并不区分尾调用中调用的是函数自身还是其他函数。不过,这个优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生栈帧。
注意之所以要求严格模式,主要因为在非严格模式下函数调用中允许使用 f . arguments 和 f . caller ,而它们都会引用外部函数的栈帧。显然,这意味着不能应用优化了。因此尾调用优化要求必须在严格模式下有效,以防止引用这些属性。
尾调用优化的代码
可以通过把简单的递归函数转换为待优化的代码来加深对尾调用优化的理解。下面是一个通过递归计算斐波纳契数列的函数:\
function fib ( n )( if ( n <2){ return n ;
return fib ( n -1)+ fib ( n )
}
console . log ( fib (0));//0 console . log ( fib (1));//1
console . log ( fib (2));//1
console . log ( fib (3));//2
console . log ( fib (4));//3
console . log ( fib (5));//5
console . log ( fib (6));//8
显然这个函数不符合尾调用优化的条件,因为返回语句中有一个相加的操作。结果, fib ( n )的栈
的内存复杂度是 O (2”)。因此,即使这么一个简单的调用也可以给浏览器带来麻烦:
fib (1000);
当然,解决这个问题也有不同的策略,比如把递归改写成迭代循环形式。不过,也可以保持递归实现,但将其重构为满足优化条件的形式。为此可以使用两个嵌套的函数,外部函数作为基础框架,内部函数执行递归:
" use strict ";
//基础框架
function fib ( n ){
return fibImpl (0,1, n ); 10
}
//执行递归
function fibImpl ( a , b , n )
if ( n =中=0){
return a ;
return fibImpl ( b , a + b , n -1);
}
这样重构之后,就可以满足尾调用优化的所有条件,再调用 fib (1000)就不会对浏览器造成威胁了。