2011-09-18 119 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測試了上面的代碼。但我認爲它可以適用於所有使用靜態堆棧大小的虛擬機。

相關問題