尾调用优化(Tail Call Optimization)
尾调用是指函数的最后一条语句是函数调用,比如下面的代码:
function doSomething() {
return doSomethingElse();
}
在ES5中,尾调用和其他形式的函数调用一样:脚本引擎创建一个新的函数栈帧并且压在当前调用的函数的栈帧上面。也就是说,在整个函数栈上,每一个函数栈帧都会被保存,这有可能造成调用栈占用内存过大甚至溢出。
尾递归在ES6中有何不同(How Tail Call are Different in ECMAScript6)
在ES6中,strict模式下,满足以下条件,尾调用优化会开启,此时引擎不会创建一个新的栈帧,而是清除当前栈帧的数据并复用:
(1、尾调用函数不需要访问当前栈帧中的变量
(2、尾调用返回后,函数没有语句需要继续执行
(3、尾调用的结果就是函数的返回值
下面例子中的函数就可以使用尾调用优化:
"use strict"; function doSomething() {
return doSomethingElse();
}
然而下面例子中的函数不能使用尾调用优化,因为尾调用的结果不是函数的返回值:
"use strict"; function doSomething() {
doSomethingElse();
}
尾调用返回后,如果函数仍然有语句要执行,也是不能使用尾调用优化的:
"use strict"; function doSomething() {
return 1 + doSomethingElse();
}
尾调用函数如果是闭包函数,也不能使用尾调用优化:
"use strict"; function doSomething() {
var num = 1;
var func = () => num;
return func();
}
如何利用尾调用优化(How to Harness Tail Call Optimization)
尾调用优化主要用在递归函数中,通常能带来显著的效果。看一个递归计算阶乘的函数:
function factorial(n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
很明显,上面的代码并不能使用尾调用优化,因为factorial(n-1)返回后,仍然有语句要继续执行。但是我们可以改写这个函数,使其能够利用尾调用优化特性:
function factorial(n, p = 1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p;
return factorial(n - 1, result);
}
}
如和改写递归函数以便利用尾调用优化是需要技巧的。如果可以,应该充分利用引擎的尾调用优化特性来优化递归函数。