尾调用优化的意义
ES6 规范新增了一项内存管理优化机制,让JavaScript引擎在满足条件时可以重用栈帧。具体来说,这项优化非常适合“尾调用”,即外部函数的返回值是内部函数的返回值,比如:
function outerFunction() {
return innnerFunction()
}
在ES6优化之前,执行这个例子会在内存中发生如下操作:
(1)执行到 outerFunction函数体,第一个栈帧被推到栈上。
(2)执行 outerFunction函数体,到 return 语句。计算返回值必须先计算 innerFunction。
(3)执行到innerFunction函数体,第二个栈帧被推到栈上。
(4)执行innerFunction函数体,计算其返回值。
(5)将栈帧弹出栈外。
在ES6优化之后,执行这个例子内存发生的变化如下:
(1)执行到 outerFunction函数体,第一个栈帧被推到栈上。
(2)执行 outerFunction函数体,到 return 语句。为求值返回值,必须先计算 innerFunction。
(3)引擎返现把第一个栈帧弹出栈外也没有问题,因为innerFunction的返回值也是outerFunction的返回值。
(4)弹出outerFunction的栈帧。
(5)执行到innerFunction函数体,栈帧被推到栈上。
(6)执行innerFunction函数体,计算其返回值。
(7)将innerFunction的栈帧弹出栈外。
总结: 很明显,第一种情况下每多调用一次嵌套循环函数,就会增加一个栈帧。第二种情况下无论调用多少次嵌套函数,都只有一个栈帧。这就是ES6尾调用优化的关键:如果函数的逻辑允许基于尾调用将其销毁,则引擎就会这么做。
尾调用优化的条件
(1)代码在严格模式下执行;
(2)外部函数的返回值是对尾调用函数的调用;
(3)尾调用函数返回后不需要执行额外的逻辑;
(4)尾调用函数不是引用外部函数作用中自由变量的闭包;
之所以要求严格模式,主要因为在非严格模式下函数调用中允许使用f.arguments和f.caller,而他们都会引用外部函数的栈帧,因此不能应用ES6中的优化。