笔记参考javascript.info中文站
递归和堆栈
函数内调用自身就是递归
1. 两种思考方式
- for 循环迭代
- 递归调用
第一种方式很简单不说,第二种递归调用有本质的不同
举个例子:
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) ); // 8
通过递归实现数字的幂次运算,可以看到函数内有两个分支:
n == 1
时,返回 x 本身,递归结束- 其他情况,返回 x * 函数递归调用,递归继续,但标志变量 n 减一
也就是说,递归函数实际上将问题不断分化成更细小简单的问题,直到最原始的初态,而每个函数都有个判定标准,每次递归时会判断这个标准是否到了最简化状态,从而可以停止递归
最大的嵌套调用次数(包括首次)被称为 递归深度,Javascript 可靠的深度范围是在 10000 以内
2. 执行上下文和堆栈
递归调用过程的底层原理是什么样的呢?
在递归函数运行中,函数有关的信息会存储在 “执行上下文” 这个数据结构中,函数执行中所有的详细细节都会放在其中
事实上,这个过程有点类似计算机组成原理的中断,只不过中断后处理的结果会返回给原操作(函数):
- 当前函数被暂停;
- 与它关联的执行上下文被一个叫做 执行上下文堆栈 的特殊数据结构保存;
- 执行嵌套调用;
- 嵌套调用结束后,从堆栈中恢复之前的执行上下文,并从停止的位置恢复外部函数。
当然,递归不能是无限的,最好控制在 10000 以内,因此函数就需要一个 “出口”,也就是函数结束的条件,如上个例子中 n == 1
就是一个出口
在这之前,所有信息都会存储在 “执行上下文” 堆栈中
此外,任何递归都可以被重写成循环形式,有的时候这种重写可以提高效率或者可读性,但递归的写法非常简洁,转化成循环形式可能反而变得复杂,尤其是 “出口” 或 “分支” 多的时候
3. 递归遍历
递归的另一个重要应用就是递归遍历。
如果遍历对象只是普通对象或者简单数组,一个循环就可以完成遍历,但如果遍历对象含有嵌套内容哪可能就会相对麻烦,如果嵌套层数过多,就需要递归遍历来解决嵌套遍历问题
递归遍历和递归函数的本质思想都是 “划分子问题” ,将整体拆解成最简单的初态再处理,最后层层返回
举个例子:
let company = {
// 这是一个公司的人力管理图,有子部门嵌套,现在我们要求所有人的薪资总数
sales: [{
name: 'John', salary: 1000}