Подтвердить что ты не робот

Размер стека вызовов в JavaScript

Я хочу проверить наличие больших стеков вызовов. В частности, я хочу, чтобы консольное предупреждение, когда длина стека вызовов достигает 1000. Это обычно означает, что я сделал что-то глупое и может привести к тонким ошибкам.

Можно ли вычислить длину стека вызовов в JavaScript?

4b9b3361

Ответ 1

Здесь функция, которая будет работать во всех основных браузерах, хотя она не будет работать в строгом режиме 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

ОБНОВЛЕНИЕ 1 ноября 2011

В строгом режиме ES5 просто не удается перейти в стек вызовов. Единственный вариант - это проанализировать строку, возвращаемую new Error().stack, которая не является стандартной, не поддерживается повсеместно и явно проблематична, и даже этот может быть невозможен навсегда.

ОБНОВЛЕНИЕ 13 августа 2013 г.

Этот метод также ограничен тем фактом, что функция, которая вызывается более одного раза в одном стеке вызовов (например, через рекурсию), бросает 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;
}

Ответ 2

Вы можете использовать этот модуль: https://github.com/stacktracejs/stacktrace.js

Вызов printStackTrace возвращает трассировку стека внутри массива, тогда вы можете проверить ее длину:

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

Ответ 3

Другой подход - это измерение доступного размера в стеке в кадре верхнего уровня, а затем определение используемого пространства в стеке путем наблюдения за тем, сколько свободного места доступно. В коде:

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) определение использованного пространства стека является потенциально дорогостоящей операцией, когда виртуальная машина имеет большой размер стека и

(2) числа, о которых сообщалось, не обязательно являются числом рекурсий, а вместо этого являются измерением фактического пространства, используемого в стеке (конечно, это также может быть преимуществом). Я видел автоматически сгенерированный код, который содержит функции, которые используют одно и то же пространство в стеке для каждой рекурсии как 2000 рекурсий функции stackSizeExplorer выше.

Примечание. Я тестировал только код выше с помощью node.js. Но я предполагаю, что он будет работать со всеми виртуальными машинами, использующими размер статического стека.