我想测试大型调用栈。具体来说,我希望在调用栈长度达到1000时收到控制台警告。这通常意味着我做了一些愚蠢的事情,并可能导致微妙的错误。
我能否在 JavaScript 中计算调用栈长度?
我能否在 JavaScript 中计算调用栈长度?
这是一个在所有主要浏览器中都可以使用的函数,但在ECMAScript 5严格模式下无法使用,因为arguments.callee
和caller
已在严格模式中删除。
function getCallStackSize() {
var count = 0, fn = arguments.callee;
while ( (fn = fn.caller) ) {
count++;
}
return count;
}
例子:
function f() { g(); }
function g() { h(); }
function h() { alert(getCallStackSize()); }
f(); // Alerts 3
2011年11月1日更新
在ES5严格模式中,根本无法遍历调用堆栈。唯一的选择是解析new Error().stack
返回的字符串,这是非标准的,不被普遍支持和明显存在问题,而且可能永远无法实现。
2013年8月13日更新
该方法也受到限制,因为在单个调用堆栈中多次调用的函数(例如通过递归)将使getCallStackSize()
陷入无限循环(由评论中的@Randomblue指出)。改进版本的getCallStackSize()
如下:它跟踪它之前看到的函数以避免进入无限循环。然而,返回的值是在遇到重复之前调用堆栈中不同函数对象的数量,而不是完整调用堆栈的真实大小。不幸的是,这是你所能做的最好的了。
var arrayContains = Array.prototype.indexOf ?
function(arr, val) {
return arr.indexOf(val) > -1;
} :
function(arr, val) {
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] === val) {
return true;
}
}
return false;
};
function getCallStackSize() {
var count = 0, fn = arguments.callee, functionsSeen = [fn];
while ( (fn = fn.caller) && !arrayContains(functionsSeen, fn) ) {
functionsSeen.push(fn);
count++;
}
return count;
}
6
,但那是因为使用控制台时似乎还执行了3个其他函数。 - pimvdbgetCallStackSize
不会返回。 - Randombluecaller
属性,因此如果一个函数调用了自身,那么它的caller
属性就是对自身的引用,因此while
循环永远不会退出。你可以通过将fn
与fn.caller
进行比较来检查这一点(http://jsfiddle.net/3DNUw/2/),但返回的调用堆栈大小不再准确。此外,这也无法处理递归调用链的情况(http://jsfiddle.net/3DNUw/3/)。解决这个问题的方法是保持已经看到的函数列表,但调用堆栈大小仍然可能不准确。 - Tim Down另一种方法是在顶层堆栈帧中测量堆栈上的可用空间,然后通过观察可用空间减少的数量来确定堆栈上已使用的空间。 代码如下:
function getRemainingStackSize()
{
var i = 0;
function stackSizeExplorer() {
i++;
stackSizeExplorer();
}
try {
stackSizeExplorer();
} catch (e) {
return i;
}
}
var baselineRemStackSize = getRemainingStackSize();
var largestSeenStackSize = 0;
function getStackSize()
{
var sz = baselineRemStackSize - getRemainingStackSize();
if (largestSeenStackSize < sz)
largestSeenStackSize = sz;
return sz;
}
例如:
function ackermann(m, n)
{
if (m == 0) {
console.log("Stack Size: " + getStackSize());
return n + 1;
}
if (n == 0)
return ackermann(m - 1, 1);
return ackermann(m - 1, ackermann(m, n-1));
}
function main()
{
var m, n;
for (var m = 0; m < 4; m++)
for (var n = 0; n < 5; n++)
console.log("A(" + m + ", " + n + ") = " + ackermann(m, n));
console.log("Deepest recursion: " + largestSeenStackSize + " (" +
(baselineRemStackSize-largestSeenStackSize) + " left)");
}
main();
stackSizeExplorer
函数的2000次递归相同的堆栈空间的函数。var trace = printStackTrace();
console.log(trace.length());
console.trace()
。 - Poelinca Dorinarguments.callee
或caller
的情况下完成。请参见我的修订答案。 - Tim Down