2011-09-18 52 views
40

我想测试大型调用堆栈。具体来说,当调用堆栈长度达到1000时,我想要一个控制台警告。这通常意味着我做了一些愚蠢的事情,并可能导致微妙的错误。在JavaScript中调用堆栈大小

我可以在JavaScript中计算调用堆栈的长度吗?

+2

[this](http://eriwen.com/javascript/js-stack-trace/)有帮助吗? –

+0

代码Dave Newton指出抛出一个异常,将其捕获为'e'并基于浏览器检查其属性。对于Chrome和Mozilla,它使用'e.stack',对于Opera 10+,它使用'e.stacktrace';对于其他人,它会尝试理解'e.message'属性。 – 2011-09-18 15:25:09

+0

错误堆栈跟踪只能放弃10个堆栈条目吗? http://jsfiddle.net/pimvdb/AuyP7/ – pimvdb

回答

45

这是一个可以在所有主流浏览器中工作的功能,虽然它不能在ECMAScript 5严格模式下工作,因为arguments.calleecaller已经在严格模式下被删除。

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 

UPDATE 2011

在ES5严格模式11月1日,根本就no way to navigate the call stack。剩下的唯一选择是解析由new Error().stack返回的字符串,这是非标准的,没有普遍支持并且显然有问题,甚至是这个may not be possible for ever

UPDATE 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; 
} 
+9

+1 Nice解决方案。以防万一任何人遇到这个问题:在Chrome的开发者工具中,它警告'6',但那是因为在使用控制台时在幕后执行看似3个其他功能。 – pimvdb

+0

不错的一个!那么,在ES5严格模式下,他们认定这个callstack太危险了?也许这只是为了防止改变callstack的默认行为。 –

+0

@Fred arguments.caller/arguments.callee在您想要进行函数内联和尾部调用优化(这在ES.next中是必需的)时变得有趣。 – gsnedders

1

您可以使用此模块: https://github.com/stacktracejs/stacktrace.js

调用的printStackTrace返回数组里面的堆栈跟踪,那么你可以检查它的长度:

var trace = printStackTrace(); 
console.log(trace.length()); 
1

一种不同的方法是测量可用然后通过观察可用空间的多少来确定堆栈上的已用空间。在代码:

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(); 

当然也有两个主要缺点的这种方法:

(1)确定所述用完堆栈空间是一个潜在的一个昂贵的操作当虚拟机具有较大的堆栈大小时,报告的数字不一定是递归的数量,而是测量实际使用的空间在堆栈上(当然,这也是一个优点)。我已经看到了自动生成的代码,其中包含的函数使用上面的stackSizeExplorer函数的2000次递归在堆栈上使用相同的空间。

注意:我只用node.js测试了上面的代码。但我认为它可以适用于所有使用静态堆栈大小的虚拟机。

相关问题