遞歸函數就是會直接或者間接地調用自身的一種函數。遞歸是一種強大的編程技術,它把一問題分解爲一組類似的子問題,每個都用一個尋常解去解決。通常來講,一個遞歸函數調用自身去解決它的子問題。node
書上的一個小例子「漢諾塔」。塔上有3根柱子和一個套直徑都不一樣的空心圓盤。開始時源柱子的全部圓盤都按照從小到大的順序堆疊。目標是每次移動一個圓盤到另外一根柱子,最終把一堆圓盤移動到目標柱子上,期間不容許把較大的圓盤放在較小的圓盤上。編程
var hanoi = function(disc, src, aux, dst){ if (disc > 0) { hanoi(disc - 1, src, dst, aux); document.writeln('Move disc' + disc + ' form ' + src + ' to ' + dst); hanoi(disc - 1, aux, src, dst); } }; hanoi(3, 'src', 'aux', 'dst');
運行代碼結果:segmentfault
Move disc1 form src to dst Move disc2 form src to aux Move disc1 form dst to aux Move disc3 form src to dst Move disc1 form aux to src Move disc2 form aux to dst Move disc1 form src to dst
hanoi
函數把一堆圓盤從一根柱子移動到另一根柱子,必要使用輔助的柱子。它把該問題拆分紅3個小問題。它先移動到一對圓盤中較小的圓盤到輔助柱子上,從而露出下面較大的圓盤,而後移動下面的圓盤到目標柱子上。最後,他將剛纔較小的圓盤從輔助柱子上再移動到目標柱子上。經過遞推地調用自身去處理一堆圓盤的移動,從而解決那些問題。hanoi
函數的參數包括當前移動的圓盤的編號和它將要用到的3根柱子。當它調用自身的時候,它去處理當前正在處理的圓盤之上的圓盤。最終,他會一個不存在的圓盤編號去調用。在這樣的狀況下,他不執行任何操做。因爲該函數對非法只不理會,也不用擔憂死循環的問題。書上第二個例子是說遞歸函數能夠很是高效率的操做樹形結構,好比DOM
。每次遞歸調用是處理指定的樹的一小段。數組
// 它從某個指定的節點開始,按照 HTML 源碼中的順序訪問該數的每一個節點。 var walk_the_DOM = function(node, func){ func(node); node = node.firstChild; while (node) { walk_the_DOM(node, func); node = node.nextSibling; } }; // 它調用 walk_the_DOM 函數,傳遞一個用來查找節點屬性名的函數爲參數。 // 匹配的節點會累加到一個結果數組裏面。 var getElementsByAttribute = function(att, val){ var results = []; walk_the_DOM(document.body, function(node){ var actual = node.nodeType === 1 && node.getAttribute(att); if (typeof actual === 'string' && (actual === val || typeof value != 'string')) { results.push(node); } }); return results; };
有一些語言提供了尾遞歸的優化。這意味着若是一個函數返回自身遞歸的結果,那麼調用的過程會被替換成以循環,能夠提升速度。惋惜js
並無尾遞歸的優化。函數
好運的是,ES6給咱們帶來了尾遞歸,詳細迎接ECMAScript 6, 使用尾遞歸。
拓展:什麼狀況下用遞歸?。優化