關於堆疊的溢位問題,在Javascript日常開發中很常見,Google了下,相關問題還是比較多的。本文旨在描述如何解決此類問題。 首先看一個例項(當然你可以使用更容易的方式實現,這裡我們僅探討遞迴):
function isEven (num) { if (num === 0) { return true; } if (num === 1) { return false; } return isEven(Math.abs(num) - 2); } //Outputs: true console.log(isEven(10)); //Outputs: false console.log(isEven(9));
當我們把引數改成10000時,執行下例會發生堆疊溢位:
function isEven (num) { if (num === 0) { return true; } if (num === 1) { return false; } return isEven(Math.abs(num) - 2); } //不同的javascript引擎報錯可能不同 //Outputs: Uncaught RangeError: Maximum call stack size exceeded console.log(isEven(10000));
原因是每次執行程式碼時,都會分配一定尺寸的棧空間(Windows系統中為1M),每次方法呼叫時都會在棧裡儲存一定資訊(如引數、區域性變數、返回值等等),這些資訊再少也會佔用一定空間,成千上萬個此類空間累積起來,自然就超過執行緒的棧空間了。那麼如何解決此類問題?
使用閉包:
function isEven (num) { if (num === 0) { return true; } if (num === 1) { return false; } return function() { return isEven(Math.abs(num) - 2); } } //Outputs: true console.log(isEven(4)()());
此時每次呼叫時,返回一個匿名函式,匿名函式執行相關的引數和區域性變數將會釋放,不會額外增加堆疊大小。
優化呼叫:
上例呼叫比較麻煩,優化如下:
function isEven (num) { if (num === 0) { return true; } if (num === 1) { return false; } return function() { return isEven(Math.abs(num) - 2); } } function trampoline (func, arg) { var value = func(arg); while(typeof value === "function") { value = value(); } return value; } //Outputs: true console.log(trampoline(isEven, 10000)); //Outputs: false console.log(trampoline(isEven, 10001));
現在我們可以解決堆疊溢位問題了,但是不是感覺每次tarmpoline(isEven, 1000)這種呼叫方式不是很好,我們可以使用bind來繫結:
function isEven(n) { /** * [isEvenInner 遞迴] * @param {[type]} num [description] * @return {Boolean} [description] */ function isEvenInner (n) { if (n === 0) { return true; } if (n === 1) { return false; } return function() { return isEvenInner(Math.abs(n) - 2); } } /** * [trampoline 迭代] * @param {[type]} func [description] * @param {[type]} arg [description] * @return {[type]} [description] */ function trampoline (func, arg) { var value = func(arg); while(typeof value === "function") { value = value(); } return value; } return trampoline.bind(null, isEvenInner)(n); } //Outputs: true console.log(isEven(10000)); //Outputs: false console.log(isEven(10001));
雖然上例實現了我們想要的效果,但是trampoline函式還是有一定的侷限性:
1.假設你只傳遞一個引數給遞迴函式
value = func(arg); 修改為 value = func.apply(func, arg);
2.假設最後的返回值不是一個函式 關於更健壯性的實現,請看underscore-contrib中原始碼。
感謝您的閱讀,文中不妥之處還望批評指正,文章已同步至個人部落格如果你有好的建議,歡迎留言,麼麼噠!
轉載宣告:
本文標題:Javascript中遞迴造成的堆疊溢位及解決方案
本文連結:http://www.zuojj.com/archives/1115.html,轉載請註明轉自Benjamin-專注前端開發和使用者體驗