JS递归

707 阅读2分钟

递归:  函数在运行过程中自己调用自己

递归算法的基本思想是:把规模大的、较难解决的问题变成规模较小的、易解决的同一问题。规模较小的问题又变成规模更小的问题,并且小到一定程度可以直接得出它的解,从而得到原来问题的解。

看一个典型的求阶乘的栗子:

function fun(n) {
    if (n<=1) { 
        return 1; 
    }else { 
        return n*fun(n-1); 
    } 
} fun(3);

但这样有个缺点:

我们知道函数名是指向函数对象的指针,如果把函数的名与函数对象本身的指向关系断开,此方式将无法找到正确的指向。

    //切断函数名关联 
    var newff = fun; 
    fun= null; 
    newff()// 报错

20190824224834916.png

如何优化呢?我们可以通过arguments.callee来代替函数名,可以确保无论怎样调用函数都不会出问题。

function fun(n) {
    if (n<=1) {
        return 1; 
    }else { 
    return n*arguments.callee(n-1); 
    } 
}

但在严格模式(use strict)下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式(内联函数)来达成相同的结果:

var fun = (function fun1(n) { 
    if (n<=1) {
        return 1;
    }else {
        return n*fun1(n-1); 
    } 
});

但其实类似求阶乘的问题并不适合用递归,因为它的运行需要较多次数的函数调用,如果调用层数比较深,每次都要创建新的变量,需要增加额外的堆栈处理,会对执行效率有一定影响,占用过多的内存资源。还有一种解决办法是尾递归

function fun(n,total=1){
    if(n===1)return total;
    return fun(n-1,n*total);
}

这样,每一次返回的就是一个新的函数,不带上一个函数的参数,也就不需要储存上一个函数了。

一个问题要采用递归方法来解决时,必须符合以下三个条件:

  1. 解决问题时,可以把一个问题转化为一个新的问题,而这个新的问题的解决方法仍与原问题的解法相同,只是所处理的对象有所不同,这些被处理的对象之间是有规律的递增或递减;
  2. 可以通过转化过程是问题得到解决;
  3. 必定要有一个明确的结束递归的条件,否则递归将会无止境地进行下去,直到耗尽系统资源。也就是说必须要某个终止递归的条件。