# 执行上下文和堆栈
现在我们来研究一下递归调用是如何工作的。为此,我们会先看看函数底层的工作原理。
有关正在运行的函数的执行过程的相关信息被存储在其 执行上下文 中。
执行上下文 (opens new window) 是一个内部数据结构,它包含有关函数执行时的详细细节:当前控制流所在的位置,当前的变量,this 的值(此处我们不使用它),以及其它的一些内部细节。
一个函数调用仅具有一个与其相关联的执行上下文。
当一个函数进行嵌套调用时,将发生以下的事儿:
- 当前函数被暂停;
- 与它关联的执行上下文被一个叫做 执行上下文堆栈 的特殊数据结构保存;
- 执行嵌套调用;
- 嵌套调用结束后,从堆栈中恢复之前的执行上下文,并从停止的位置恢复外部函数。
# 尾调用
尾调用指的是函数的返回另一个函数的调用
# 尾递归
函数尾调用自身,就是尾递归
下面代码就是尾递归
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) );
任何递归都可以用循环来重写。通常循环变体更有效。
……但有时重写很难,尤其是函数根据条件使用不同的子调用,然后合并它们的结果,或者分支比较复杂时。而且有些优化可能没有必要,完全不值得。
递归可以使代码更短,更易于理解和维护。并不是每个地方都需要优化,大多数时候我们需要一个好代码,这就是为什么要使用它。
# 尾递归优化
# 蹦床函数
function trampoline(f) {
while (f && typeof f === 'function') {
f = f()
}
return f
}
function f(n, m = 1, o = 1) {
if (n <= 2) {
return o
}
return f.bind(null, n - 1, o, m + o)
}
trampoline(f(40)) // 102334155
trampoline(f(100000)) // Infinity
# 深度遍历算法
function tco(func) {
var value
var active = false
const accumulated = []
return function accumulator(...args) {
accumulated.push(args)
if (!active) {
active = true
while (accumulated.length) {
value = func.apply(this, accumulated.shift())
}
active = false
return value
}
}
}
const f = tco(function fibonacci(n, m = 1, o = 1) {
if (n <= 2) {
return o
}
return f(n - 1, o, m + o)
})
f(40) // 102334155
f(100000) // Infinity