调用栈

控制流经过函数的方式有点复杂。 让我们仔细看看它。 这是一个简单的程序,它执行了一些函数调用:

  1. function greet(who) {
  2. console.log("Hello " + who);
  3. }
  4. greet("Harry");
  5. console.log("Bye");

这个程序的执行大致是这样的:对greet的调用使控制流跳转到该函数的开始(第 2 行)。 该函数调用控制台的console.log来完成它的工作,然后将控制流返回到第 2 行。 它到达greet函数的末尾,所以它返回到调用它的地方,这是第 4 行。 之后的一行再次调用console.log。 之后,程序结束。

我们可以使用下图表示出控制流:

  1. not in function
  2. in greet
  3. in console.log
  4. in greet
  5. not in function
  6. in console.log
  7. not in function

由于函数在返回时必须跳回调用它的地方,因此计算机必须记住调用发生处上下文。 在一种情况下,console.log完成后必须返回greet函数。 在另一种情况下,它返回到程序的结尾。

计算机存储此上下文的地方是调用栈。 每次调用函数时,当前上下文都存储在此栈的顶部。 当函数返回时,它会从栈中删除顶部上下文,并使用该上下文继续执行。

存储这个栈需要计算机内存中的空间。 当栈变得太大时,计算机将失败,并显示“栈空间不足”或“递归太多”等消息。 下面的代码通过向计算机提出一个非常困难的问题来说明这一点,这个问题会导致两个函数之间的无限的来回调用。 相反,如果计算机有无限的栈,它将会是无限的。 事实上,我们将耗尽空间,或者“把栈顶破”。

  1. function chicken() {
  2. return egg();
  3. }
  4. function egg() {
  5. return chicken();
  6. }
  7. console.log(chicken() + " came first.");
  8. // → ??