0Pricing
JavaScript Academy · 课时

调用栈

了解系统如何跟踪函数调用。

调用栈 是 CoddyKit 上的免费 JavaScript Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 JavaScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 JavaScript Academy 课程共包含 4 节课。

什么是调用栈

调用栈用于让 JavaScript 跟踪当前正在运行的函数,以及运行结束后应返回的位置。每次函数调用都会被压入栈顶;函数完成后则会被弹出。

它是一种 LIFO 结构:后进先出。

压栈与出栈

调用函数时,会压入一个栈帧。函数返回时,该栈帧会被弹出。请观察嵌套调用时的日志顺序。

function first() {
  console.log('first start');
  second();
  console.log('first end');
}

function second() {
  console.log('second');
}

first();

LIFO 的实际运行

最近调用的函数必须先完成,位于它下方的函数才能继续执行。内部调用虽然最后开始,却会最先完成。

function a() {
  console.log('enter a');
  b();
  console.log('exit a');
}
function b() {
  console.log('enter b');
  c();
  console.log('exit b');
}
function c() {
  console.log('c runs');
}
a();

同步意味着阻塞

JavaScript 在单线程上一次只执行一件事。只要栈中还有栈帧,就不会运行其他内容。同步代码会一直阻塞,直到执行完毕。

console.log('start');

function busy() {
  let sum = 0;
  for (let i = 0; i < 1000; i++) sum += i;
  return sum;
}

console.log(busy());
console.log('end');

返回值向下传递

每个栈帧弹出时,其返回值都会回传给调用栈中下方等待它的调用者。

function square(n) {
  return n * n;
}
function sumOfSquares(a, b) {
  return square(a) + square(b);
}

console.log(sumOfSquares(3, 4));

递归会增长调用栈

每次递归调用都会增加一个栈帧。调用栈会不断增长,直到遇到基准情况,然后逐帧展开。

function countdown(n) {
  if (n === 0) {
    console.log('liftoff');
    return;
  }
  console.log(n);
  countdown(n - 1);
}

countdown(3);

栈溢出

调用栈的大小是有限的。没有基准情况的递归会不断压入栈帧,直到引擎抛出 RangeError:调用栈大小超出限制。

function endless(n) {
  return endless(n + 1);
}

try {
  endless(1);
} catch (e) {
  console.log(e.name);
}

执行顺序是可预测的

由于调用栈严格遵循 LIFO,并且代码是同步执行的,同步日志的顺序始终是确定的。您可以手动追踪这一顺序。

function outer() {
  console.log(1);
  inner();
  console.log(3);
}
function inner() {
  console.log(2);
}
outer();
console.log(4);

异步从何处介入

调用栈只运行同步代码。setTimeout 等函数会将工作交给运行环境,运行环境随后将回调放入队列。只有当调用栈为空时,这些回调才会运行。

这就是通向事件循环的桥梁。

console.log('A');
setTimeout(() => console.log('B (later)'), 0);
console.log('C');

必须先清空调用栈

即使是零毫秒的定时器,也必须等待所有同步代码执行完毕。只要栈中还有栈帧,回调就无法运行。

console.log('sync 1');
setTimeout(() => console.log('timeout'), 0);
console.log('sync 2');
console.log('sync 3');

调用栈为何重要

理解调用栈可以解释为什么漫长的同步循环会卡住页面、为什么递归可能导致栈溢出,以及为什么异步回调总是最后运行。它是事件循环中所有机制的基础。

function compute() {
  return [1, 2, 3].reduce((a, b) => a + b, 0);
}
console.log('before');
console.log(compute());
console.log('after');

快速检查

调用栈遵循什么顺序原则?

回顾:调用栈

您已经学会了引擎的执行核心:

  • 调用栈按照 LIFO 顺序跟踪正在运行的函数。
  • JavaScript 是单线程的,同步代码会阻塞执行。
  • 递归会使调用栈增长,并可能导致栈溢出。
  • 异步回调会等待调用栈变空,从而引出事件循环。

常见问题解答

「调用栈」课时是免费的吗?

是的 — 「调用栈」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 JavaScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 JavaScript Academy 课程共包含 4 节课。

「调用栈」这节课中我会学到什么?

了解系统如何跟踪函数调用。 你通过在浏览器中直接运行的动手代码来练习 JavaScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 JavaScript Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 JavaScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「调用栈」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 JavaScript Academy 课中编写并运行代码吗?

能。每节 JavaScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 调用栈
  2. 宏任务与任务队列
  3. 微任务与 Promise
  4. queueMicrotask 与执行顺序
← 返回 JavaScript Academy