在 JS 语句中,以下几种情况可能包含尾调用: + 代码块中(由 {} 分隔的语句) + if 语句的 then 或 else 块中 + do-while,while,for 循环的循环体中 + switch 语句的执行代码块中 + try-catch 语句的 catch 块中 + try-finally,try-catch-finally 语句的 finally 块中
此外,return 语句也可以包含尾调用,如果 return 的表达式包含尾调用,return 语句就包含尾调用,这就不用多解释了。
单独的函数调用不是尾调用
下面这个函数是否包含尾调用呢:
function foo() { bar() }
答案是否定的,还是先将函数改写一下:
function foo() { bar() return undefined }
可以看到 return 语句返回的只是一个 undefined 而并非 bar 函数的返回值,所以这里不存在尾调用。
尾调用只能出现在严格模式中
在非严格模式中,大多数引擎会在函数上增加下面两个属性: + func.arguments 包含调用函数时传入的参数 + func.caller 返回当前函数的调用者
但一旦进行了尾调用优化,中间调用帧会被丢弃,这两个属性也就失去了本来的意义,这也是在严格模式中不允许使用这两个属性的原因。
堆栈信息丢失
除了开发者难以辨别尾调用以外,另一个原因则是堆栈信息会在优化的过程中丢失,这对于调试是不方便的,另外一些依赖于堆栈错误信息来进行用户信息收集分析的工具可能会失效。针对这个问题,实现一个影子堆栈可以解决堆栈信息缺失的问题,但这中解决方式相当于对堆栈进行了模拟,不能保证始终符合实际虚拟机堆栈的真实状态。另外,影子堆栈的性能开销也是非常大的。
基于以上原因,V8 团队建议使用特殊的语法来指定尾递归优化,TC39 标准委员会有一个还没有结论的提案叫做从语法上指定尾部调行为,这个提案由来自 Mozilla 和微软的委员提出。提案的具体内容可以看链接,主要是提出了三种手动指定尾调用优化的语法。
附手动优化语法
Return Continue
function factorial(n, acc = 1) { if (n === 1) { return acc; } return continue factorial(n - 1, acc * n) } let factorial = (n, acc = 1) => continue n == 1 ? acc : factorial(n - 1, acc * n); // or, if continue is an expression form: let factorial = (n, acc = 1) => n == 1 ? acc : continue factorial(n - 1, acc * n);
Function sigil
// # sigil, though it's already 'claimed' by private state. #function() { /* all calls in tail position are tail calls */ } // Note that it's hard to decide how to readably sigil arrow functions. // This is probably most readable. () #=> expr // This is probably most in line with the non-arrow sigil. #() => expr // rec sigil similar to async functions rec function() { /* likewise */ } rec () => expr
!-return
function () { !return expr } // It's a little tricky to do arrow functions in this method. // Obviously, we cannot push the ! into the expression, and even // function level sigils are pretty ugly. // Since ! already has a strong meaning, it's hard to read this as // a tail recursive function, rather than an expression. !() => expr // We could do like we did for # above, but it also reads strangely: () !=> expr