JavaScript中的递归函数调用

18

我知道在JavaScript中进行递归调用函数时应该小心谨慎,因为第二次调用可能会慢上多达10倍。

《JavaScript编程精解》指出:

有一个重要的问题:在大多数JavaScript实现中,第二个版本比第一个版本慢约10倍。在JavaScript中,运行简单的循环比多次调用函数要便宜得多。

John Resig甚至在这篇文章中也提到了这个问题。

我的问题是:为什么使用递归效率如此低下?这只是特定引擎的构建方式吗?我们是否会在JavaScript中看到这种情况不再存在的时候?


1
我猜这是一个作用域的问题 :P 很棒的问题,我很好奇他们的答案是什么! - Pelshoff
4
函数调用的开销比循环控制大,这在几乎所有编程语言中都是如此。调用函数时需要进行更多的簿记工作:分配和初始化新的作用域,保存返回地址,整理参数等。请注意,JavaScript解释器一直以来速度提升非常迅速,因此应该质疑3年前博客文章(或书籍)中的性能技巧是否仍然适用。 - Pointy
1
“因为你的第二个调用可能会慢上多达10倍。” 这并不是文本所说的。它说第二个代码版本要慢10倍。 - Rag
这是一个很好的演示,展示了递归可以有多慢。请注意,不仅仅是函数调用造成了差异,因为两个测试都具有相同数量的调用。 - Jake
2个回答

11

由于所有涉及更改堆栈和设置新上下文等的开销,函数调用比简单循环更加昂贵。为了使递归非常有效,语言必须支持某种形式的尾调用消除,这基本上意味着将某些递归函数转换为循环。OCaml、Haskell和Scheme等函数式语言做到了这点,但我所知道的JavaScript实现都没有这样做(除非它们全部都这样做,否则这只会略微有用,所以也许我们面临哲学家就餐问题)。


5
ES6应该具备尾调用优化。合适的尾调用 - Raynos

3
这只是特定 JS 引擎的构建方式。如果没有尾递归消除,每次进行递归都需要创建新的堆栈帧,而循环只需将程序计数器重置到其开头。例如,Scheme 将此作为语言规范的一部分,因此您可以使用这种方式的递归而不必担心性能问题。https://bugzilla.mozilla.org/show_bug.cgi?id=445363 显示 Firefox 正在取得进展(Brendan Eich 在其中谈到了可能将其纳入 ECMAScript 规范的可能性),但我认为当前没有任何一个浏览器完全实现了它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接