我有一个递归函数,耗尽调用堆栈是我有时遇到的问题。我知道我可以使用流、promise 和 setTimeout,但我只想编写触发尾调用优化的代码。到目前为止,似乎只有 Webkit 实现了尾部调用优化(TCO)。除了了解理论之外,还有什么方法可以检查我的代码是否会触发 TCO,无论是使用 devtools 还是通过检查 Webkit 编译器的输出?
我知道它并不完美,但我认为这是我们目前最好的选择。
考虑这个功能:
"use strict";
function detectTCO() {
const outerStackLen = new Error().stack.length;
// name of the inner function mustn't be longer than the outer!
return (function inner() {
const innerStackLen = new Error().stack.length;
return innerStackLen <= outerStackLen;
}());
}
Error.stack
不是标准的,但它在所有现代浏览器中都有基本支持。尽管如此,它在不同的浏览器中具有不同的值,当一个较长的命名函数尾部调用一个较短的命名函数时,堆栈跟踪:
截至目前,这对于 Safari 会返回
true
,对于其他浏览器会返回 false
,这是当前的 TCO 支持。
在节点 REPL 中,
Error.stackTraceLimit
默认为 10,并且在调用 detectTCO
时已经满了。因此,当内堆叠框架被推到顶部时,底部框架被推出,因此整体长度可以变小。为了防止这种情况,可以将限制增加 Error.stackTraceLimit = 30;
或通过从 detectCTO
调用它来使用干净的堆栈运行 setTimeout
。
"use strict";
function detectTCO() {
const outerStackLen = new Error().stack.length;
// name of the inner function mustn't be longer than the outer!
return (function inner() {
const innerStackLen = new Error().stack.length;
return innerStackLen <= outerStackLen;
}());
}
document.getElementById('result').innerText = detectTCO() ? 'Yes' : 'No';
#result {
color: blue;
font-weight: bold;
}
Is TCO available?
<div id="result"></div>
可以使用
.toString()
、 RegExp.test()
来检查 return
语句后跟下划线、或 $ 符号、a-z 字符; 后跟左括号,后跟任意字符,后跟右括号
function forEach(arr, callback, start) {
if (0 <= start && start < arr.length) {
callback(arr[start], start, arr);
return forEach(arr, callback, start + 1); // tail call
}
}
console.log(/return [_a-z$]+\(.*\)/i.test(forEach.toString()))
你总是可以用最愚蠢的方式来做到这一点 - 在 try、catch 块中运行带有一些“大”参数的尾递归函数。如果抛出异常,则 TCO 不起作用。