js递归函数解析:阶乘示例

递归

程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

来说一个经典的递归调用编程题——阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。
亦即n!=1×2×3×…×(n-1)×n。
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n。

1、通过函数名自身递归调用

function factorial(num){
	if(num <= 1){
		return 1;
	}
	return num*factorial(num-1);
}
factorial(5); //120

执行过程:
①代码执行factorial(5)–>进入函数,此时num=5;递归前进。
②执行5*factorial(4),此时代码等待,执行factorial(4)–>进入函数,num=4;递归前进。
③执行4*factorial(3),此时代码等待,执行factorial(3)–>进入函数,num=3;递归前进。
④执行3*factorial(2),此时代码等待,执行factorial(2)–>进入函数,num=2;递归前进。
⑤执行2*factorial(1),此时代码等待,执行factorial(1)–>进入函数,num=1;触发边界条件。
⑥执行if条件判断语句,返回1;此时factorial(1)的执行结果是1;开始递归返回,按以下代码顺序依次执行,每次返回的结果与当前的num相乘。

2*factorial(1)-->2×1//2
3*factorial(2)-->3×2×1//6
4*factorial(3)-->4×3×2×1//24
5*factorial(4)-->5×4×3×2×1//120

最后的结果是120。
来看一下控制台的debug:
在这里插入图片描述
这种通过函数名调用自身的方式存在一个问题:函数名是一个指向函数对象的指针,函数的执行与函数名factorial紧紧耦合在了一起,如果我们把函数名与函数对象本身的指向关系断开,这种方式运行时将出现错误。如下代码:

function factorial(num){
	if(num <= 1){
		return 1;
	}
	return num*factorial(num-1);
}
factorial(5); //120
//将factorial指针指向factorialNew
var factorialNew = factorial;
factorialNew(5); //120
//将factorial重新定义
factorial = function(){
	return 0;
};
factorialNew(5); //0

2、通过arguments.callee调用函数自身

callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。

function factorial(num){
	if(num <= 1){
		return 1;
	}
	return num*arguments.callee(num-1);
}
factorial(5); //120

在这个重写后的 factorial() 函数的函数体内,没有再引用函数名 factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。这时候如果重新将 factorial 赋值,结果还是120。如下代码:

//将factorial指针指向factorialNew
var factorialNew = factorial;
factorialNew(5); //120
//将factorial重新定义
factorial = function(){
	return 0;
};
factorialNew(5); //120

在此,变量 factorialNew 获得了 factorial 的值,实际上是在 factorialNew 上保存了 factorial 函数的引用。然后,我们又将一个返回数值0的函数赋值给 factorial 变量。如果像原来的 factorial() 那样不使用 arguments.callee,调用 factorialNew()就会返回数值0。可是,在解除了函数体内的代码与函数名的耦合状态之后,factorialNew()就能够正常地计算阶乘,因为它调用的是之前保存的引用。至于 factorial(),它现在只是一个返回数值0的函数。

参考:arguments.callee 属性包含当前正在执行的函数

这种方式很好的解决了函数名指向变更时导致递归调用找不到自身的问题。
但是这种方式也不是很完美,因为在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。

3、通过命名函数表达式来实现arguments.callee的效果

'use strict'
var factorialIIFE = (function() {
	return function factorial(num) {
		if (num <= 1) {
			return 1;
		} else {
			return num * factorial(num - 1);
		}
	}
})();
factorialIIFE(5); //120

var factorialNew = factorialIIFE;
factorialNew(5); //120

factorialIIFE = function(){
	return 0;
};
factorialNew(5); //120

在上面的代码中,阶乘函数factorial被封装成了一个IIEF(立即调用的函数表达式);赋值给变量名为factorialIIFE的变量。这里函数执行的结果和使用arguments.callee执行的结果是一样的。

这里我理解的也不是很明白,但是还是要说一下我自己的理解思路,如果不正确,还请大家帮忙指正一下。

思路:因为这里使用的是函数名调用,所以使用IIEF将阶乘函数factorial封装为一个闭包,而由于闭包的特性,factorial绑定了当前的函数作用域;factorial()引用的就是当前正在执行的函数。还记得本文第一部分的内容么:函数名是一个指向函数对象的指针,函数的执行与函数名紧紧耦合在了一起;我们利用闭包将factorial与factorial的调用紧紧联系在一起,并将引用赋值给了factorialNew,所以即便将factorialIIFE重新赋值(引用并未修改),也无法改变factorialNew的调用结果。

发布了45 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ThisEqualThis/article/details/103037957
今日推荐