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 聚焦于理解同步代码所需的执行轨迹:活动栈帧、绑定、外部环境、函数调用、函数返回和控制台输出。它是学习模型,并不宣称暴露浏览器引擎的私有内存布局。
先做预测会更有效:现在哪个上下文处于活动状态?一个名字会在哪里被找到?接下来会返回到哪里?然后逐步运行可视化器,把预测与模型对照。