1、什么是递归函数?
递归函数就是在一个函数通过名字调用自身的情况下构成的,
如下所示:我们用递归实现阶乘
var factorial = function (num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } }
2、如何实现递归函数?
(1)先写一层的情况。上面所示的递归阶乘我们就可以先思考num参数乘num-1的情况。
(2)抽象递归参数。在递归函数中,如何将下一层关联起来就需要抽象参数来进行解决,参数的个数根据情况而定。
(3)找到突破点。为什么要找一个突破点?在js中你会发现很少有人写递归,因为不注意就会造成死循环。必须记住递归函数是自己调用自己,某种情况(这种情况肯定存在)不调用自己。在上面的递归函数中num<=1就是一个突破点。
3、上述代码存在的问题
var anotherfactorial = factorial; factorial = null; console.log(anotherfactorial(3)); // factorial is not a function(出错)
为什么会出错呢?
以上代码先把factorial函数保存在变量anotherfactorial中,然后将factorial赋值为null。导致的结果指向原始函数的引用只剩下一个。但是接下来调用anotherfactorial时,由于必须执行factorial函数,而factorial这时候已经赋值为null了,所以就会导致错误。
那么如何解决这个问题呢?
在ECMAScript提供了arguments.callee可以解决这个问题。arguments.callee是一个指向正在执行的函数(anotherfactorial)的指针,因此可以用它来实现对函数的递归调用。例如上述代码我们就可以这样写:
var factorial = function (num) { if (num <= 1) { return 1; } else { return num * arguments.callee(num - 1); } } var anotherfactorial = factorial; factorial = null; console.log(anotherfactorial(3)); // 6
通过使用arguments.callee代替函数名,可以确保无论怎样调用函数都不会出问题。因此在使用递归函数时,使用arguments.callee总比使用函数名更保险。
但是在严格模式下,不能直接访问arguments.callee,并且会导致错误。所以产生了终极解决方案,使用命名函数表达式来达成相同的效果。例如:
var factorial = (function f(num) { if (num <= 1) { return 1; } else { return num * f(num - 1); } }); var anotherfactorial = factorial; factorial = null; console.log(anotherfactorial(3)); // 6
在以上代码中创建了一个名为f的命名函数表达式,避免了出现arguments.callee。然后将它赋值给变量factorial。即使把函数赋值给另一个变量,函数的名字f仍然有效,所以递归调用照样能正确完成,而且这种方法在严格模式和非严格模式下都能运行。