2011-09-18 17 views
40

Quiero probar pilas de llamadas grandes. Específicamente, quiero una advertencia de consola cuando la longitud de la pila de llamadas llega a 1000. Esto generalmente significa que hice algo estúpido, y puede conducir a errores sutiles.Tamaño de la pila de llamadas dentro de JavaScript

¿Puedo calcular la longitud de la pila de llamadas dentro de JavaScript?

+2

¿[esto] (http://eriwen.com/javascript/js-stack-trace/) ayuda? –

+0

El código que Dave Newton señala arroja una excepción, lo captura como 'e' e inspecciona sus propiedades, basado en el navegador. Para Chrome y Mozilla, usa 'e.stack', para Opera 10+ usa' e.stacktrace' y para otros intentará darle sentido a la propiedad 'e.message'. –

+0

¿La traza de la pila de errores no da solo hasta 10 entradas de pila? http://jsfiddle.net/pimvdb/AuyP7/ – pimvdb

Respuesta

45

Esta es una función que funcionará en todos los principales navegadores, aunque no funcionará en el modo estricto ECMAScript 5 porque arguments.callee y caller se han eliminado en modo estricto.

function getCallStackSize() { 
    var count = 0, fn = arguments.callee; 
    while ((fn = fn.caller)) { 
     count++; 
    } 
    return count; 
} 

Ejemplo:

function f() { g(); }  
function g() { h(); }  
function h() { alert(getCallStackSize()); }  

f(); // Alerts 3 

ACTUALIZA 1 de noviembre de 2,011

En modo estricto ES5, no es simplemente no way to navigate the call stack. La única opción que queda es analizar la cadena devuelta por new Error().stack, que no es estándar, no es universalmente compatible y obviamente problemática, e incluso esta may not be possible for ever.

Actualización 13 de agosto de 2013

Este método también está limitado por el hecho de que una función que se llama más de una vez en una pila única llamada (por ejemplo, a través de la recursión) tirará getCallStackSize() en un bucle infinito (como señalado por @Randomblue en los comentarios). A continuación se muestra una versión mejorada de getCallStackSize(): realiza un seguimiento de las funciones que ha visto antes para evitar entrar en un bucle infinito. Sin embargo, el valor devuelto es el número de objetos de función diferentes en la pila de llamadas antes de encontrar una repetición en lugar del verdadero tamaño de la pila de llamadas completa. Esto es lo mejor que puedes hacer, desafortunadamente.

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 Buena solución. En caso de que alguien experimente este problema: en las Herramientas para Desarrolladores de Chrome alerta '6', pero eso se debe a que aparentemente hay otras 3 funciones que se ejecutan detrás de escena cuando se usa la consola. – pimvdb

+0

¡lindo! Entonces, ¿en el modo estricto de ES5, decidieron que la pila de llamadas es demasiado peligrosa? tal vez esto solo sea para evitar alteraciones en el comportamiento predeterminado de la callstack. –

+0

@Fred arguments.caller/arguments.callee se vuelve divertido cuando se quiere hacer una función de alineación y optimización de la cola de llamada (que es obligatoria en ES.next). – gsnedders

1

Puede utilizar este módulo: https://github.com/stacktracejs/stacktrace.js

Calling printStackTrace devuelve el seguimiento de la pila dentro de una matriz, entonces usted puede comprobar su longitud:

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

Un enfoque diferente es la medición de la disposición tamaño en la pila en el marco de la pila de nivel superior y luego determinar el espacio utilizado en la pila observando cuánto menos espacio hay disponible. En código:

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

Por ejemplo:

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

Por supuesto, hay dos principales desventajas de este enfoque:

(1) determinar el espacio de pila utilizado es un potencialmente una operación costosa cuando la VM tiene un gran tamaño de pila y

(2) los números informados no son necesariamente el número de recursiones, sino que son una medida del espacio real utilizado en la pila (por supuesto, esto también puede ser una ventaja). He visto un código generado automáticamente que contiene funciones que usan el mismo espacio en la pila por recursión como 2000 recursiones de la función stackSizeExplorer anterior.

Nota: Solo he probado el código anterior con node.js.Pero supongo que funcionaría con todas las máquinas virtuales que usan un tamaño de pila estática.

Cuestiones relacionadas