JavaScript 运行时指南

JavaScript 如何执行代码:从全局代码到函数返回

用准确且可操作的模型理解 JavaScript 的执行上下文、函数调用、词法环境与调用栈。

JavaScript 在执行上下文中求值可执行代码。全局代码从一个执行上下文开始;每次调用普通函数,都会创建另一个执行函数体的上下文。

这篇文章说明 Deep JS 可视化器使用的学习模型:在有帮助时采用规范术语,但重点始终是你能亲自检查的执行过程,而不是假装展示某个引擎的私有实现细节。

一句话回答

  • 全局代码与函数体都在执行上下文中运行。
  • 调用函数会把新的工作放到调用者之上的调用栈;函数返回后,调用者继续执行。
  • 读取标识符时,JavaScript 会沿词法嵌套的环境查找,而不是按函数实际从哪里被调用来查找。

1. 从可执行代码与执行上下文开始

脚本或函数体被求值前,引擎会准备该代码运行期间处于活动状态的执行上下文。这个上下文关联着用于标识符解析的词法环境,以及与当前代码相应的 this 绑定。

顶层脚本代码运行时,全局执行上下文处于活动状态;只有函数被调用时,函数执行上下文才会处于活动状态。可视化器把这些活动上下文画成栈帧,让工作顺序变得可见。

2. 准备阶段与求值阶段不是同一件事

在代码开始求值时,某些声明可以已经可用,即使对应语句还没有执行到。这解释了为什么函数声明有时可以在其源码位置之前被调用。

但这不代表所有值都会被“自动赋到文件顶部”。var 绑定、词法声明、函数声明和 class 声明的初始化规则并不相同。

调用表达式求值前,函数声明已经完成初始化

sayHello("Ada");

function sayHello(name) {
  console.log(`Hello, ${name}`);
}

3. 一次函数调用会创建一层嵌套工作

当求值进行到调用表达式时,调用者会暂停,直到被调用函数完成。函数调用会创建函数执行上下文,按语言规则初始化参数与局部绑定,并把这层工作放到调用者之上。

函数完成后,它的上下文会从活动调用栈中移除,调用者从下一项操作继续。调用栈记录的是正在进行的工作,不是所有已经存在的函数。

外层 console.log 完成前,format 会先运行

function format(name) {
  return `Hello, ${name}`;
}

console.log(format("Ada"));

4. 标识符查找遵循词法结构

在 speak 内部,greeting 不是局部绑定。JavaScript 会先检查 speak 的环境,再沿外部环境链接走到创建 speak 的环境,并在那里找到 greeting。

这个外部链接由函数的词法创建位置决定。把调用表达式移到另一个调用者中,不会改变 greeting 的解析位置。

greeting 在外部词法环境中被找到

const greeting = "Hello";

function speak(name) {
  console.log(`${greeting}, ${name}`);
}

speak("Ada");

5. 可视化器展示什么

Deep JS 聚焦于理解同步代码所需的执行轨迹:活动栈帧、绑定、外部环境、函数调用、函数返回和控制台输出。它是学习模型,并不宣称暴露浏览器引擎的私有内存布局。

先做预测会更有效:现在哪个上下文处于活动状态?一个名字会在哪里被找到?接下来会返回到哪里?然后逐步运行可视化器,把预测与模型对照。

亲自试一试在 Deep JS 中运行这个模型编辑程序并逐步运行,检查活动调用栈、执行上下文、环境链接与控制台输出。