递归: 函数在运行过程中自己调用自己
递归算法的基本思想是:把规模大的、较难解决的问题变成规模较小的、易解决的同一问题。规模较小的问题又变成规模更小的问题,并且小到一定程度可以直接得出它的解,从而得到原来问题的解。
看一个典型的求阶乘的栗子:
function fun(n) {
if (n<=1) {
return 1;
}else {
return n*fun(n-1);
}
}
fun(3);
但这样有个缺点:
我们知道函数名是指向函数对象的指针,如果把函数的名与函数对象本身的指向关系断开,此方式将无法找到正确的指向。
//切断函数名关联
var newff = fun;
fun= null;
newff()// 报错
如何优化呢?我们可以通过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.必定要有一个明确的结束递归的条件,否则递归将会无止境地进行下去,直到耗尽系统资源。也就是说必须要某个终止递归的条件。