调用栈
了解系统如何跟踪函数调用。
调用栈 是 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 反馈 — 无需本地设置。