JavaScript中的调用栈大小

Call stack size within JavaScript

本文关键字:调用 JavaScript      更新时间:2023-09-26

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

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

这是一个可以在所有主流浏览器中工作的函数,尽管它不能在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 1 November 2011

在ES5严格模式下,根本没有办法导航调用堆栈。剩下的唯一选择是解析new Error().stack返回的字符串,这是非标准的,不是普遍支持的,显然有问题,甚至这可能永远不可能。

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

你可以使用这个模块:https://github.com/stacktracejs/stacktrace.js

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

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

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

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。