JavaScript调用栈是单线程下追踪函数执行顺序的LIFO结构,仅记录调用关系与返回地址,不存储数据;每次函数调用生成栈帧(含参数、变量、行号、作用域链、this),执行完即弹出;异步任务需等待栈空后由事件循环推入。
JavaScript调用栈是引擎用来追踪函数执行顺序的后进先出(LIFO)内存结构,它不存储数据本身,而是记录“谁在调用谁、该回到哪里继续执行”。它的核心作用不是保存变量值,而是保证函数能按正确顺序进入、暂停、恢复和退出。
每次函数被调用,引擎就创建一个栈帧(Stack Frame),并把它压入调用栈顶部。这个栈帧里封装了当前函数运行所需的最小上下文:
函数一执行完,对应栈帧立刻弹出,控制权交还给下面那个还没结束的函数——就像关掉一层抽屉,自动露出下一层。
因为 JavaScript 是单线程,同一时刻只能干一件事,调用栈就是这个“唯一通道”的调度员:
a() → 创建 a 的栈帧,压入栈顶没有调用栈,函数嵌套就会失去返回路径,程序根本无法知道“下一步该跑哪行”。
当你看到浏览器报错里的 at foo (script.js:5:3),那一长串就是调用栈快照——从最内层出错函数一路回溯到全局;而 RangeError: Maximum call stack size exceeded,说明栈帧塞得太满,通常是递归没终止或深层嵌套所致。栈大小由引擎限制(如 V8 约 10k–20k 层),超出即崩。
setTimeout、Promise 回调、fetch 响应这些异步任务不会进调用栈。它们先被挂起,等调用栈完全清空后,才由事件循环从任务队列取出、新建执行上下文、再推入调用栈。所以异步代码永远出现在“栈空之后”,这也是为什么 console.log('start') 总比 setTimeout(() => console.log('end'), 0) 先打印。