遞歸函數就是會直接或者間接調用自身的一種函數。遞歸是一種強大的編程技術,它把一個問題分解為一組相似的子問題,調用自身去解決它的子問題。
一、漢諾塔
問題描述:有3根柱子和一套直徑各不相同的空心圓盤。開始時源柱子上的所有圓盤都按照從小到大的順序堆疊。目標是通過每次移動一個圓盤到另一根柱子,最終把一堆圓盤移動到目標柱子上,過程中不允許把較大的圓盤放置在較小的圓盤上。
var hanoi=function(disc,src,aux,dst){ if(disc>0){ hanoi(disc-1,src,dst,aux); console.log("move "+disc+" from "+src+" to "+dst); hanoi(disc-1,aux,src,dst); } }
圓盤數量為3的時候解法為:
hanoi函數把一堆圓盤從一根柱子移動到另一根柱子,必要時使用輔助的柱子。
它把問題分解成3個子問題:
首先,移動一對圓盤中較小的圓盤到輔助柱子上,從而露出下面較大的圓盤。
然后,移動下面較大的圓盤到目標柱子上。
最后,它將剛才較小的圓盤從輔助的柱子上再移動到目標柱子上。
傳遞給hanoi函數的參數包括當前移動的圓盤編號和它將用到的3根柱子。當它調用自身的時候,它去處理當前正在處理的圓盤之上的圓盤。最終,它會以一個不存在的圓盤編號去調用。此時,不執行任何操作。由於該函數對非法值不予理會,就不用擔心它會導致死循環。
二、DOM遍歷
遞歸函數可以非常高效地操作樹形結構,比如瀏覽器文檔對象模型DOM。每次遞歸調用處理指定樹的一小段。
html結構如下:
<body> <div class="test">測試div</div> <span class="test">測試span</span> <div class="test1">test1 div</div> </body>
js如下:
<script> /*定義walk_the_DOM函數,它從某個指定的節點開始,按HTML源碼中的順序訪問該樹的每個節點。 它會調用一個函數,並依次傳遞每個節點給它。walk_the_DOM調用自身去處理每一個子節點。 */ var walk_the_DOM=function walk(node,func){ func(node); node=node.firstChild; while(node){ walk(node,func); node=node.nextSibling; } } /*定義getElementsByAttribute函數。它以一個屬性名稱字符串和一個可選的匹配值作為參數。 它調用walk_the_DOM,傳遞一個用來查找節點屬性名的函數作為參數。 匹配的節點會累加到一個結果數組中。 */ var getElementsByAttribute=function(att,value){ var results=[]; walk_the_DOM(document.body,function(node){ var actual=node.nodeType===1&&node.getAttribute(att); if(typeof actual==='string' &&( actual===value|| typeof value!=='string')){ results.push(node); } }); return results; } console.log(getElementsByAttribute("class","test"));//[div.test, span.test]
三、命名函數表達式和遞歸
1、遞歸問題
求階乘的函數:
function factorial(num){ if(num<=1){ return 1; }else{ return num*factorial(num-1); } }
正常情況運行沒問題,但是下面操作會讓它出錯:
var anotherFactorial=factorial;//把函數保存在遍歷anotherFactorial中 factorial=null;//factorial置為null,此時指向原始函數的引用只剩一個 anotherFactorial(3)
factorial已經不再是函數,所以會報錯。
2、arguments.callee實現遞歸
arguments.callee是一個指向正在執行的函數的指針,因此可以用它來實現對函數的遞歸調用
//console function factorial(num){ if(num<=1){ return 1; }else{ return num*arguments.callee(num-1); } } var anotherFactorial=factorial; factorial=null; anotherFactorial(3) //6
用arguments.callee代替函數名,可以確保無論怎樣調用函數都不會出問題。因此,在編寫遞歸函數時,使用arguments.callee總比使用函數名更保險。
問題:在嚴格模式下,不能通過腳本訪問arguments.callee,訪問這個屬性會報錯。
更多嚴格模式相關內容可參考:javascript 語句和嚴格模式(三)
3、命名函數表達式實現遞歸
創建一個名為f()的命名函數表達式,然后賦值給factorial,即使把函數賦值給了另一個變量,函數的名字f仍然有效,所以遞歸調用照樣能正常完成。
這種方式在嚴格模式和非嚴格模式都可行。
//console var factorial =function f(num){ 'use strict' if(num<=1){ return 1; }else{ return num*f(num-1); } } factorial(3) //6 var anotherFactorial=factorial; factorial=null; anotherFactorial(3) //6
命名函數表達式也有它的一些經典bug,見javascript 函數和作用域(函數,this)(六)
本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載注明出處:http://www.cnblogs.com/starof/p/6510723.html有問題歡迎與我討論,共同進步。