Javascript中遞歸形成的堆棧溢出及解決方案

關於堆棧的溢出問題,在Javascript平常開發中很常見,Google了下,相關問題仍是比較多的。本文旨在描述如何解決此類問題。 首先看一個實例(固然你可使用更容易的方式實現,這裏咱們僅探討遞歸):javascript

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時,運行下例會發生堆棧溢出:html

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

此時每次調用時,返回一個匿名函數,匿名函數執行相關的參數和局部變量將會釋放,不會額外增長堆棧大小。java

優化調用:

上例調用比較麻煩,優化以下:git

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來綁定:github

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.假設你只傳遞一個參數給遞歸函數app

value = func(arg); 修改成 value = func.apply(func, arg);函數

2.假設最後的返回值不是一個函數 關於更健壯性的實現,請看underscore-contrib中源碼。優化

感謝您的閱讀,文中不妥之處還望批評指正,文章已同步至我的博客若是你有好的建議,歡迎留言,麼麼噠!

轉載聲明:

本文標題:Javascript中遞歸形成的堆棧溢出及解決方案

本文連接:http://www.zuojj.com/archives/1115.html,轉載請註明轉自Benjamin-專一前端開發和用戶體驗

相關文章
相關標籤/搜索