koucxz / blog

前端学习和工作的知识整理
19 stars 1 forks source link

递归函数的执行 #2

Open koucxz opened 7 years ago

koucxz commented 7 years ago

递归(Recursion)

一个函数可以指向并调用自身(call itself)。有三种方法可以达到这个目的:

  1. 函数名
  2. arguments.callee
  3. 作用域下的一个指向该函数的变量名

上述概念引用自MDN,对递归概念不清楚的可以自行查看;

递归函数的执行

在这里我们讨论一下递归函数中递归后的语句如何执行,先看这样一个例子:

function rec(x){
if(x!==1){
   console.log(x)
   rec(x-1)
   console.log(x) 
   }   
}
rec(5) //输出为5 4 3 2 2 3 4 5

以上这段代码执行的结果就是递归前的语句顺序执行,递归后的语句倒序执行。初看到这段代码,完全不能理解它执行的顺序。通过调试让代码逐行执行,可以看到执行的顺序,其实就是一层一层执行递归,每执行到rec(x-1)时就重新执行该函数,递归后的语句会在递归执行到最里层后再由内向外输出。 CSDN上的一篇博客很好的总结了递归的特性如下: 1 每一次函数调用都会有一次返回.当程序流执行到某一级递归的结尾处时,它会转移到前一级递归紧接着的后面继续执行. 2 递归函数中,位于递归调用前的语句和各级被调函数具有相同的顺序.如打印语句 #1 位于递归调用语句前,它按照递归调用的顺序被执行了 4 次. 3 每一级的函数调用都有自己的私有变量. 4 递归函数中,位于递归调用语句后的语句的执行顺序和各个被调用函数的顺序相反. 5 虽然每一级递归有自己的变量,但是函数代码并不会得到复制. 6 递归函数中必须包含可以终止递归调用的语句.

在这个例子中,终止递归调用的条件是if(x!==1),如果调用时取值x小于1,会构成死循环;这个例子中的递归采用了通过函数名调用的方法。 下面我们再讨论一下argumengts.callee方式,在ES5严格模式下,callee是无法使用的,原因详见MDN中arguments.callee

这是callee的一个独特的用法,在这个结构下无法替代,

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
}
var anotherFactorial = factorial;
alert(anotherFactorial(4)); //出错!

但我们可以用下面这种方式,把递归函数赋值给一个变量: var factorial = (function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } });