当 JavaScript 引擎开始执行一段代码时,它并不是“看到一行就执行一行”,而是先建立一套有序的追踪和管理机制。这套机制的核心就是执行上下文(Execution Context)——可以把它理解为代码运行时的“工作环境”,它记录了当前代码能够访问到的变量、函数、参数以及 this 的值。
每次代码执行都离不开执行上下文,不同类型的代码会创建不同类型的执行上下文。JavaScript 引擎通过一个执行上下文栈(调用栈) 来管理这些上下文——新的上下文被推入栈顶,执行完毕后弹出销毁,程序恢复到上一个上下文继续执行。
理解执行上下文是理解作用域、闭包、变量提升、this 绑定等一系列核心机制的起点。
3.1.1 全局执行上下文(Global Execution Context)
全局上下文是 JavaScript 代码运行的最外层环境。当一段 .js 文件开始执行,或者你在 <script> 标签、浏览器控制台中输入代码时,首先创建的就是全局执行上下文。
在全局上下文中:
- 全局对象被创建:浏览器中为
window,Node.js 中为global。 this指向全局对象(严格模式下为undefined,但全局代码中的 this 仍为全局对象)。- 所有在全局作用域中声明的
var变量和函数声明,都会成为全局对象的属性;而let、const声明则挂在全局块作用域下,不会成为全局对象的属性。
var a = 1;
let b = 2;
console.log(window.a); // 1 (浏览器环境)
console.log(window.b); // undefined
全局上下文的生命周期
从脚本开始执行时创建,直到页面关闭或程序退出时销毁。一个程序中只有一个全局执行上下文。
3.1.2 函数执行上下文(Function Execution Context)
每调用一次函数,都会创建一个专属的函数执行上下文。这是 JavaScript 中最常见的上下文类型。注意:函数定义时并不创建上下文,只有在调用时才创建。
函数上下文中会包含:
- 参数传递:形参作为局部变量绑定,
arguments对象在非箭头函数中可用。 - 内部声明的变量和函数:它们只在这个函数被调用期间存在。
- 作用域链:记录当前上下文可以访问的外部环境,这是第 3.4 节要深入讨论的内容。
this的绑定:根据调用方式确定的 this 值(详见第 5 章)。
下面是一个简单的例子:
function greet(name) {
let message = "Hello, " + name;
return message;
}
greet("Alice");
// 调用时创建函数执行上下文
// 完成后上下文弹出销毁,message 不再存在
函数执行上下文的创建和销毁非常频繁,这也是为什么不能过度依赖在函数内部创建大量大对象——频繁的创建和垃圾回收会影响性能。
3.1.3 Eval 执行上下文(Eval Execution Context)
在 eval() 函数中执行的代码也会创建自己的执行上下文。eval 可以接收一段字符串,并在当前作用域中执行它:
let x = 1;
eval("let y = 2; console.log(x + y)"); // 3
eval 上下文属于一种特殊、动态的执行环境,它会修改当前作用域,并且会降低引擎执行效率(因为引擎无法事先对其进行优化)。在严格模式下,eval 有自己独立的作用域,不会污染外层变量。
为什么不建议使用 eval
- 性能代价:JavaScript 引擎的许多优化(如作用域分析、预测)在面对
eval时会失效,因为引擎无法预测eval中的字符串会改变什么。 - 安全风险:执行任意字符串内容极易导致代码注入攻击。
- 可读性极差:字符串内嵌代码难以调试和维护。
除非在极其特殊的元编程场景下(例如实现一个沙箱环境),现代 JavaScript 开发中几乎不会再使用 eval 执行上下文。本书后续也不会再围绕它展开详细讨论,你只需要知道它的存在和风险即可。
3.1.4 执行上下文的生命周期
每个执行上下文(无论全局、函数还是 eval)都会经历相似的三个阶段:
- 创建阶段
- 创建变量对象(Variable Object):收集当前作用域中的变量声明、函数声明、形参等。
- 建立作用域链:确定当前上下文可以向哪些外部环境查找变量。
- 确定
this的指向。 - 此时代码还未执行,但变量和函数已经被“提升”(var 变量初始化为 undefined,let/const 变量进入暂时性死区)。
- 执行阶段
- 逐行执行代码,进行变量赋值、运算、函数调用等操作。
- 销毁阶段
- 执行完成后从调用栈弹出,所占内存被标记为可回收,由垃圾回收机制处理。
这三个阶段中,最容易被忽视却又最重要的,就是创建阶段的变量对象和工作原理(变量提升的本质),下一节 3.2 将带你深入了解执行栈的运行机制,3.3 节会详细拆解变量对象与活动对象,看看提升到底是怎么发生的。