刚才在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/