javascript遞歸函數理解和說明

遞歸函數通常在後端用的比較多。對於後端開發人員來說,遞歸應該是小菜一碟,很簡單的事情,但是很多前端確對這個不是很瞭解。其實,前端中也是經常用遞歸的,最簡單的例子,我之前的一篇博客,講過setTimeout()其中,setTimeout中讓一個數沒個一秒增加1,不斷重複執行,就是一個簡單的遞歸! 說白了,遞歸就是函數自己調用自己。很簡單,沒有那麼可怕!js的另外一個難點就是閉包,閉包和遞歸,很多前端望而生畏,關於閉包

js遞歸調用

// 一個簡單的階乘函數  
var f = function (x) {  
    if (x === 1) {  
        return 1;  
    } else {  
        return x * f(x - 1);  
    }  
};  

Javascript中函數的巨大靈活性,導致在遞歸時使用函數名遇到困難,對於上面的變量式聲明,f是一個變量,所以它的值很容易被替換:

var fn = f;  
f = function () {};  

函數是個值,它被賦給fn,我們期待使用fn(5)可以計算出一個數值,但是由於函數內部依然引用的是變量f,於是它不能正常工作了。

所以,一旦我們定義了一個遞歸函數,便須注意不要輕易改變變量的名字。

上面談論的都是函數式調用,函數還有其它調用方式,比如當作對象方法調用。

我們常常這樣聲明對象:

var obj1 = {  
    num : 5,  
    fac : function (x) {  
        // function body  
    }  
};  

聲明一個匿名函數並把它賦值給對象的屬性(fac)。

如果我們想要在這裏寫一個遞歸,就要引用屬性本身:

var obj1 = {  
    num : 5,  
    fac : function (x) {  
        if (x === 1) {  
            return 1;  
        } else {  
            return x * obj1.fac(x - 1);  
        }  
    }  
}; 

當然,它也會遭遇和函數調用方式一樣的問題:

var obj2 = {fac: obj1.fac};  
obj1 = {};  
obj2.fac(5); // Sadness  

方法被賦值給obj2的fac屬性後,內部依然要引用obj1.fac,於是…失敗了。

換一種方式會有所改進:

var obj1 = {  
     num : 5,  
     fac : function (x) {  
        if (x === 1) {  
            return 1;  
        } else {  
            return x * this.fac(x - 1);  
        }  
    }  
};  
var obj2 = {fac: obj1.fac};  
obj1 = {};  
obj2.fac(5); // ok  

通過this關鍵字獲取函數執行時的context中的屬性,這樣執行obj2.fac時,函數內部便會引用obj2的fac屬性。

可是函數還可以被任意修改context來調用,那就是萬能的call和apply:

obj3 = {};  
obj1.fac.call(obj3, 5); // dead again  

於是遞歸函數又不能正常工作了。

我們應該試着解決這種問題,還記得前面提到的一種函數聲明的方式嗎?

var a = function b(){};  

這種聲明方式叫做內聯函數(inline function),雖然在函數外沒有聲明變量b,但是在函數內部,是可以使用b()來調用自己的,於是

var fn = function f(x) {  
    // try if you write "var f = 0;" here  
    if (x === 1) {  
        return 1;  
    } else {  
        return x * f(x - 1);  
    }  
};  
var fn2 = fn;  
fn = null;  
fn2(5); // OK  
// here show the difference between "var f = function f() {}" and "function f() {}"  
var f = function f(x) {  
    if (x === 1) {  
        return 1;  
    } else {  
        return x * f(x - 1);  
    }  
};  
var fn2 = f;  
f = null;  
fn2(5); // OK  
var obj1 = {  
    num : 5,  
    fac : function f(x) {  
        if (x === 1) {  
            return 1;  
        } else {  
            return x * f(x - 1);  
        }  
    }  
};  
var obj2 = {fac: obj1.fac};  
obj1 = {};  
obj2.fac(5); // ok  

var obj3 = {};  
obj1.fac.call(obj3, 5); // ok  

就這樣,我們有了一個可以在內部使用的名字,而不用擔心遞歸函數被賦值給誰以及以何種方式被調用。

Javascript函數內部的arguments對象,有一個callee屬性,指向的是函數本身。因此也可以使用arguments.callee在內部調用函數:

function f(x) {  
    if (x === 1) {  
        return 1;  
    } else {  
        return x * arguments.callee(x - 1);  
    }  
}

但arguments.callee是一個已經準備被棄用的屬性,很可能會在未來的ECMAscript版本中消失,在ECMAscript 5中"use strict"時,不能使用arguments.callee。

最後一個建議是:如果要聲明一個遞歸函數,請慎用new Function這種方式,Function構造函數創建的函數在每次被調用時,都會重新編譯出一個函數,遞歸調用會引發性能問題——你會發現你的內存很快就被耗光了。

js遞歸函數應用

最近在做項目的時候,用到了遞歸函數,用來調用json的子節點,把所有json中的子節點中包含某個數的object,都push到一個數組中,然後對其進行綁定。

我是通過如下遞歸調用的

var new_array=[];
     function _getChilds(data){
         if(data.ObjType=="某個數"){
            new_array.push(cs_data);
        }
        if(data.Childs){
          if(data.Childs.length>0){
              getChilds(data.Childs)
          }
       }
     }
    function getChilds(data){
        for(var i=0;i<data.length;i++){
            _getChilds(data[i]);
        }
    }

使用方法:getChilds("json數據")

就把json中所有包含某個數的數據push到new_array數組當中了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章