在JavaScript中调用堆栈大小

时间:2011-09-18 14:22:46

标签: javascript debugging

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

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

3 个答案:

答案 0 :(得分:46)

这是一个适用于所有主流浏览器的功能,虽然它在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

2011年11月1日更新

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

2013年8月13日更新

此方法也受到以下事实的限制:在单个调用堆栈中多次调用的函数(例如通过递归)会将getCallStackSize()抛出到无限循环中(正如@Randomblue所指出的那样)评论)。下面是getCallStackSize()的改进版本:它跟踪之前看到的函数,以避免进入无限循环。但是,返回值是在遇到重复之前callstack中不同函数对象的数量,而不是完整调用堆栈的真实大小。不幸的是,这是你能做的最好的事情。

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;
}

答案 1 :(得分:2)

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

调用printStackTrace返回数组内的堆栈跟踪,然后可以检查其长度:

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

答案 2 :(得分:2)

另一种方法是测量顶层堆栈帧中堆栈的可用大小,然后通过观察可用空间少来确定堆栈上的已用空间。在代码中:

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)当VM具有大堆栈大小时,确定用完的堆栈空间可能是一项昂贵的操作

(2)报告的数字不一定是递归的数量,而是测量堆栈上使用的实际空间(当然,这也是一个优点)。我已经看到自动生成的代码包含的函数在每次递归时使用堆栈上的相同空间作为上面stackSizeExplorer函数的2000次递归。

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