定义
摘抄自维基百科:在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
从字面上的意义来说,就是在一个函数里面调用函数自己本身。
经典例子
1.阶乘
function factorial(n){
if(n == 1 || n === 0){
return 1
}
return n * factorial(n-1)
}
2.斐波那契数列
function fb(n){
if(n == 0 || n == 1){
return n
}
return fb(n-1) + fb(n-2)
}
分析
由两个例子我们可以看出,在函数内,他还会再调用函数自己本身,而调用的那一层,可能就有值可以return出来,也可能再次调用函数本身。但无论是连续调用几次,他都有一个边界,像阶乘的边界是n=1,斐波那契数列的边界时n=2。
由此我们可以总结出递归的特点:
1.有规律,有一个调用函数自己本身的过程。
2.有出口,有边界条件使得递归结束。
优缺点
优点:
能够将多次重复计算的过程用一小段代码表示出来,让代码看起来更为简洁
缺点:
1.递归是一个一次或多次调用函数本身的过程,而在函数调用时,都要在栈内存中分配空间来存储变量、参数、地址等,这些都回消耗时间和空间,从而导致执行效率低下。
2.递归存在很多重复计算的过程,有可能会出现重叠计算,这样重复的计算也会导致效率低下。
优化方法
递归就是不断调用函数的过程。而当执行一个函数时,就会创建一个执行上下文,并压入执行上下文栈,当函数执行完毕时,执行上下文会从执行上下文栈中弹出。不断调用函数的过程,javascript会不断重复上面的过程,这是非常损耗的。我们需要一个方法来优化这个过程,这个方法就是尾调用。
尾调用
尾调用的意思就是函数内部最后一个动作是函数调用,返回值只有函数,没有其他运算过程。
eg:
尾调用:
function a(x){
return f(x)
}
非尾调用:
function b(x){
return f(x) + 1
}
第一个函数执行时,虽然调用了一个函数,但原来的函数a已经执行完毕,执行上下文会弹出,再压入另外一个函数f的执行上下文。这个过程中相当于只压入一个执行上下文。也就是说函数a的执行上下文弹出后,才会压入函数f的执行上下文。
而第二个函数执行时,在调用函数的过程中,函数b需要等函数f执行完与1相加后,才有返回值,这个过程相当于压入了两个执行上下文。
尾调用的作用就是减少创建执行上下文的个数。
优化例子
我们将阶乘函数优化一下:
function factorial(n,sum){
if(n == 1 || n === 0){
return first
}
return factorial(n-1,n * sum)
}
其中sum一开始的值为阶乘的最后一个数1。