Javascript递归函数性能下降

时间:2016-02-16 16:42:40

标签: javascript performance recursion

我在招聘流程技能测试期间提出了以下问题:

var x = function(z) {
    console.log(z);    
    if (z > 0) {
        x(z-1);
    }
};
  

为什么随着z变高,这会逐渐变慢?提出一个更好的   版本,保持递归。

我想知道答案只是为了了解它。我回答说它变慢了,因为随着z的增加,递归调用的数量也增加了,但我无法提供更好的版本。另外,我不知道当z变高时功能变得更慢还有其他原因。

2 个答案:

答案 0 :(得分:11)

正确的答案是,“随着z变高,会逐渐变慢”。事实上,它并不在我的简单测试中。在任何减速可见之前,我的测试会溢出堆栈。

我无法想象在保持其递归性质的同时编写函数的任何替代方法。

答案 1 :(得分:1)

由于JavaScript没有真正的尾部调用(但是)您所遇到的内容并不是基于z的值而减速,而是基于堆栈增长和垃圾收集器(GC)无法实现的减速清理保留此功能所需的作用域堆栈。

理论上,您可以使用setImmediate和回调模式来解决此问题。使用setImmediate允许当前执行循环的范围不再使用,并在下一个GC循环期间收集。

所以,比如:

var x = function x(z){
    console.log(z);    
    if (z > 0) {
        setImmediate(function(){
          x(z-1);
        });
    }
};

但是这不起作用,因为z被传递到setImmediate的范围,因此x的前一个范围不能正确地使用GC。

相反,您必须使用IIFE(立即调用函数表达式)来结合使用setImmediate来实现您正在寻找的内容,以允许执行周期有时间允许GC:

var x = function x(z){
    console.log(z);    
    if (z > 0) {
        setImmediate((function(newZ){
          return function(){
            x(newZ);
          };
        })(z-1));
    }
};

除非出现任何错别字,否则我认为上述内容是正确的。当然,如果你使用ES6,你也可以大大简化。

这个等式的另一面是console.log的假脱机效果,缓冲区调整了你在浏览器中执行此操作的大小。在OS终端中,这些成本将被最小化,因为后备缓冲区滚动受限并且后台缓冲区已预先分配并重新使用。