什么是尾调用
ES6新增了一项内存管理机制,让Javascript引擎可以在满足条件时可以重用栈帧。这项优化非常适合“尾调用”,即外部函数的返回值是一个内部函数的返回值。
function outerFunction() {
return innerFunction()
}
在ES6优化之前,执行这个例子会在内存中发生以下操作:
-
执行到outerFunction函数体,第一个栈帧被推到栈上;
-
执行outerFunction函数体,到return语句。计算返回值必须先计算innerFunction;
-
执行到innerFunction函数体,第二个栈帧被推到栈上;
-
执行innerFunction函数体,计算其返回值;
-
将返回值传回outerFunction,然后outerFunction再返回值;
-
将栈帧弹出栈外
在ES6优化之后,执行这个例子会在内存中发生以下操作: -
执行到outerFunction函数体,第一个栈帧被推到栈上;
-
执行outerFunction函数体,到return语句。计算返回值必须先计算innerFunction;
-
引擎发现把第一个栈帧弹出栈外也没问题,因为innerFunction的返回值也就是outerFunction的返回值;
-
弹出outerFunction的栈帧
-
执行到innerFunction函数体,栈帧被推到栈上
-
执行innerFunction函数体,计算返回值
-
将innerFunction的栈帧弹出栈外
尾调用优化条件
尾调用优化条件是确定外部栈帧真的没有存在的必要了。其条件有:
- 代码在严格模式下进行;
- 外部函数的返回值是对尾调用函数的调用;
- 尾调用函数返回后不需要执行额外的逻辑;
- 尾调用函数不是引用外部函数作用域中变量的闭包;
// 无优化,尾调用没有返回
function outerFunction() {
innerFunction()
}
//无优化,尾调用没有直接返回
function outerFunction(){
let innerFunctionResult = innerFunction()
return innerFunctionResult
}
// 无优化,尾调用返回后还要转型为字符串
function outerFunction(){
return innerFunction().toString()
}
//无优化,尾调用是个闭包
function outerFunction() {
let foo = 'hello'
function innerFunction() { return foo }
return innerFunction()
}
'use strict'
//有优化:栈帧被销毁前执行参数计算
function outerFunction(a, b) {
return innerFunction(a + b)
}
// 有优化,初始返回值不涉及栈帧
function outerFunction(a, b){
if(a < b) {
return a
}
return innerFunction()
}
// 有优化,两个内部函数都在尾部
function outerFunction(condition){
return condition ? innerFunctionA() : innerFunctionB()
}
尾调用优化代码
通过把简单的递归函数转换为优化的代码来理解尾调用优化。
递归函数形式求解斐波拉契数列
function fib(n) {
if (n < 2){
return 1
}
return fib(n - 1) + fib(n - 2)
}
这个函数不满足尾调用的条件,因为最后的操作还有加法。可以将其重构为满足优化条件的形式。为此可以使用两个嵌套函数,外层函数作为基础框架,内层函数执行递归:
'use strict'
// 基础框架
function fib(n){
return fibImp(0,1,n)
}
function fibImp(a, b, n) {
if(n === 0) {
return a
}
return fibImp(b, a + b, n - 1)
}