剛纔在IBM DW上看到這篇《JavaScript 技巧與高級特性》,其中關於arguments.callee的部分有一個用遞歸來求斐波那契數列的例子,簡化一下是這樣的:javascript
這種教科書式的寫法出鏡率很高,在不少文章裏均可以看到,可是速度也特別慢,曾經看到過有些人就拿這種例子來講明「遞歸的效率低」或者「用javascript作函數式編程效率低」,而後給出迭代的寫法……java
更新:我今天老老實實的讀了SICP的第一章以後發現書中對這個問題其實有很嚴謹的解釋,爲了防止本身被罵成民科,趕忙修正了一些說法,加了刪除線的文字都是有錯誤的,新增長的文字用粗體。web
其實這個方法速度慢並非函數式編程(FP)的錯,首先要把詞義弄清楚,在程序執行的過程當中,「遞歸」(recursive)指的是一種方法,把大的複雜的問題分解成更小更簡單的問題,逐級分解下去,直到問題的規模小到能夠直接求解,而後再逐級向上回溯直到解決最初的問題。遞歸的計算過程(recursive process)包含了兩個階段,先逐級擴展(expansion),構造起一個由被推遲的操做組成的鏈條(會被解釋器保存在堆棧裏),而後在收縮(contraction)階段逐級回溯執行那些操做。隨着遞歸計算步驟的增多,這種方法消耗的資源會愈來愈大,並且會包含愈來愈多的冗餘操做,上面那個求斐波那契數列的例子(在SICP裏被稱做「樹形遞歸」)在這方面問題尤爲嚴重,由於它的計算步驟會隨着參數而指數性的增加。ajax
引用SICP上的圖解:編程
而在編程裏常說的遞歸其實就是簡單的指「本身調用本身」的過程,指的是一種語法形式,而不是計算過程,在SICP裏使用「遞歸過程」(recursive procedure)這個詞來稱呼,表示「一個過程的定義中引用了該過程自己」,在FP裏就是一個函數把狀態做爲參數反覆調用本身,用遞歸過程也能夠產生出迭代計算過程(iterative process,迭代計算過程當中消耗的資源是一個常量),遞歸==迭代,這個表達式不只在lisp,Erlang這類FP語言裏成立,在javascript裏也同樣。ide
好比那個求斐波那契數列的例子就能夠用尾遞歸:函數式編程
跟這樣的迭代方法是徹底等價的:函數
都是從數列的起始處開始遞推,區別只是:在迭代方法裏是把每兩個相鄰的數相加的和保存在循環體外部的局部變量裏,在尾遞歸方法中是把這個和做爲參數傳給下一次函數調用。優化
附帶說一下,「尾遞歸」(Tail Recursion)指的是把每次函數遞歸調用中的全部運算結果或操做都逐步傳遞到最末尾一次的函數調用,FP語言在編譯/解釋的時候都會把尾遞歸優化成一次直接的運算,而在javascript引擎裏就算沒有優化,至少也能夠在每次調用過程當中不留下任何痕跡,能夠像普通的循環語句那樣線性的推算到最後,所以不管速度仍是內存消耗,都跟普通的迭代方法沒有區別。spa
文章出自:http://www.limboy.com/2008/11/22/javascript-tail-recursion/