尾调用优化
ECMAScript 6 新增的一项内存管理优化机制, 让 JavaScript 引擎在满足条件时可以重用栈帧 非常适合于尾调用
尾调用的概念
什么是尾调用? 外部函数的返回值是内部函数的返回值, 比如:
function outerFunction () {
return innerFunction (); // 尾调用
}
使用尾调用优化和不适用尾调用优化的区别?
以上面代码块里面的代码为例:
未优化:
- 执行到 outerFunction 函数体, 第一个栈帧被推到栈上;
- 执行 outerFunction 函数体, 到 return 语句, 计算返回值必须先计算 innerFunction.
- 执行到 innerFunction 函数体, 第二个栈帧被推到栈上.
- 执行 innerFunction 函数体, 计算其返回值
- 将返回值传回 outerFunction, 然后 outerFunction 再返回值
- 将栈帧弹出栈外.
ES6 优化
- 执行到 outerFunction 函数体, 第一个栈帧被推到栈上
- 执行outerFunction 函数体, 到达 return 语句. 为求值返回语句, 必须先求值 innerFunction.
- 引擎发现把第一个栈帧弹出栈外也没问题, 因为 innerFunction 的返回值也是 outerFunction 的返回值.
- 弹出 outerFunction 的栈帧.
- 执行到 innerFunction 函数体, 栈帧被推到栈上.
- 执行 innerFunction 函数体, 计算其返回值.
- 将 innerFunction 的栈帧弹出栈外
尾调用满足的条件
一句话阐述
尾调用优化的条件: 就是确定外部栈帧真的没有必要存在了.
分条件展示
- 代码在严格模式下进行
- 外部函数的返回值是对尾调用函数的调用
- 尾调用函数返回后不需要执行额外的逻辑
- 尾调用函数不是引用外部函数作用域中自由变量的闭包
bad case
function outerFunction () {
innerFunction();
}
尾调用没有返回, 所以不属于尾调用函数
function outerFunction () {
let innerFunctionResult = innerFunction();
return innerFunctionResult;
}
尾调用没有直接返回 必须要直接返回才属于尾调用函数
function outerFunction () {
return innerFunction().toString();
}
尾调用没有直接返回, 返回后还必须转为字符串. 必须要直接返回 再没有别的操作才符合尾调用函数
function outerFunction() {
let foo = 'bar';
function innerFunction(){
return foo;
}
return innerFunction();
}
尾调用是一个闭包, 不符合尾调用函数的规定
good case
function outerFunction(a, b) {
return innerFunction(a + b);
}
符合, 栈帧销毁前执行参数计算
function outerFunction(a, b) {
if (a < b) {
return a;
}
return innerFunction(a + b);
}
符合, 初始的返回值不设计栈帧
function outerFunction() {
return condition ? innerFunctionA() : innerFunctionB();
}
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
符合, 尾调用不一定是出现在函数的尾部, 只要是最后一步操作即可
差异化尾调用和递归尾调用
引擎并不区分尾调用的是自身函数还是其他函数
不过很明显, 递归场景下的优化效果最明显, 递归最容易在栈内存中迅速产生大量栈帧.
递归案例
fib 函数, 斐波那契数列
// 经典递归
function fib(n) {
if (n < 2) {
return n;
}
return fib(n -1) + fib(n - 2)
}
现在这个明显不符合尾调用优化的条件 如何修改?
function fib(n){
return fibImpl(0, 1, n)
}
function fibImpl(a, b, n) {
if (n === 0) {
return a;
}
return fibImpl(b, a + b, n - 1)
}