调用栈和执行上下文是javascript运行时底层执行机制,事件循环依赖它们调度任务;调用栈为lifo结构,记录当前执行的函数上下文;执行上下文包含vo、作用域链和this值,是真实内存结构;事件循环仅在调用栈为空时执行微任务队列,再取宏任务。

调用栈和执行上下文是 JavaScript 运行时最底层的执行机制,它们不是“高级概念”,而是每行代码被执行时真实发生的内存与控制流操作。事件循环依赖它们才能判断“现在该执行谁、什么时候执行、谁该排队”。没有它们,异步根本无从谈起。
调用栈:函数执行的实时快照
调用栈是一个后进先出(LIFO)的结构,它不存储数据,只记录“正在执行哪段代码”——准确说是记录每个函数调用所对应的执行上下文。
- 每次函数被调用,JS 引擎就创建一个新的执行上下文,并把它压入调用栈顶部
- 函数执行完毕,该上下文立即弹出,控制权交还给栈中下一个上下文
- 栈底永远是全局执行上下文;栈顶永远是当前正在运行的函数
- 栈溢出(如无限递归)会直接抛出 RangeError: Maximum call stack size exceeded
执行上下文:代码运行的“身份证”
每个执行上下文都包含三样关键信息:变量对象(VO)、作用域链、this 值。它不是抽象概念,而是在函数调用瞬间真实分配的一块内存结构。
-
全局上下文在脚本加载时就创建,生命周期贯穿整个程序,绑定
window或globalThis - 函数上下文每次调用都新建,彼此隔离——哪怕同一个函数被调用十次,就有十个互不影响的上下文
- 创建阶段就完成变量提升(hoisting):函数声明优先于
var声明,let/const会进入“暂时性死区” - 执行阶段才真正赋值、执行语句;此时作用域链已确定,决定了变量能否被访问
为什么事件循环必须等调用栈清空?
事件循环本身不执行代码,它只是个调度器。它的唯一判断依据就是:调用栈是否为空。
- 只要栈里还有函数在跑,所有宏任务(如
setTimeout回调)和微任务(如Promise.then)都必须等待 - 栈一空,事件循环立刻检查微任务队列,一次性清空所有待执行微任务(注意:新产生的微任务也会被本轮执行)
- 微任务队列清空后,才从宏任务队列取一个任务(比如下一个
setTimeout),压入调用栈执行 - 这个“栈空 → 执行微任务 → 取宏任务”的闭环,就是事件循环的核心节奏
一个典型执行顺序示例
下面这段代码能清晰暴露三者协作关系:
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4);
输出一定是 1 → 4 → 3 → 2。原因很直接:1 和 4 是同步代码,压栈即执行;Promise.then 是微任务,等同步栈清空后立刻执行;setTimeout 是宏任务,要等到下一轮事件循环才轮到它。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











