遞歸函數通常在後端用的比較多。對於後端開發人員來說,遞歸應該是小菜一碟,很簡單的事情,但是很多前端確對這個不是很瞭解。其實,前端中也是經常用遞歸的,最簡單的例子,我之前的一篇博客,講過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數組當中了。