JavaScript 递归
一个函数可以指向并调用自身。有三种方法可以达到这个目的:
- 函数名
arguments.callee
- 作用域下的一个指向该函数的变量名
例如,思考一下如下的函数定义:
var foo = function bar() { // statements go here };
在这个函数体内,以下的语句是等价的:
bar()
arguments.callee()
foo()
调用自身的函数我们称之为递归函数。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。例如以下的循环:
var x = 0; while (x < 10) { // "x < 10" 是循环条件 // do stuff x++; }
可以被转化成一个递归函数和对其的调用:
function loop(x) { if (x >= 10) // "x >= 10" 是退出条件(等同于 "!(x < 10)") return; // 做些什么 loop(x + 1); // 递归调用 } loop(0);
不过,有些算法并不能简单的用迭代来实现。例如,获取树结构中所有的节点时,使用递归实现要容易得多:
function walkTree(node) { if (node == null) // return; // do something with node for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }
跟loop
函数相比,这里每个递归调用都产生了更多的递归。
将递归算法转换为非递归算法是可能的,不过逻辑上通常会更加复杂,而且需要使用堆栈。事实上,递归函数就使用了堆栈:函数堆栈。
这种类似堆栈的行为可以在下例中看到:
function foo(i) { if (i < 0) return; console.log('begin:' + i); foo(i - 1); console.log('end:' + i); } foo(3); // 输出: // begin:3 // begin:2 // begin:1 // begin:0 // end:0 // end:1 // end:2 // end:3