深入理解 JavaScript 执行上下文(Execution Context)
一、前言
在学习 JavaScript 的过程中,你一定遇到过这些问题:
- 为什么变量会“提升”?
- 为什么
this有时候指向全局,有时候又是对象? - 为什么闭包中的变量不会被销毁?
这些问题的本质,都离不开 执行上下文(Execution Context) 的机制。
本文将系统地梳理执行上下文的概念,深入理解变量对象、作用域链、执行上下文栈等核心组成部分。
二、执行上下文是什么?
执行上下文(Execution Context,简称 EC)是 JavaScript 代码运行的环境。
每当 JavaScript 代码运行一段可执行代码时(例如全局代码、函数代码、eval),引擎就会为其创建一个新的执行上下文,用来存储执行该代码所需的变量、函数声明、作用域链和 this 值等信息。
执行上下文主要分为三种类型:
| 类型 | 说明 |
|---|---|
| 全局执行上下文(Global EC) | 程序开始运行时创建的唯一上下文。window 或 global 对象即为其变量对象。 |
| 函数执行上下文(Function EC) | 每次函数调用时都会创建一个新的执行上下文。 |
| Eval 执行上下文(Eval EC) | 由 eval() 代码执行时创建,一般不推荐使用。 |
三、执行上下文的创建阶段
执行上下文的创建分为两个阶段:
- 创建阶段(Creation Phase)
- 执行阶段(Execution Phase)
以函数执行为例,创建阶段会完成以下三个步骤 👇:
1️⃣ 创建变量对象(Variable Object,VO)
变量对象是一个特殊的对象,用来存放:
- 函数的形参
- 函数声明
- 变量声明(但初始值为
undefined)
在函数中,变量对象也被称为活动对象(Activation Object, AO)。
1 | function test(a) { |
创建阶段中,test 的变量对象大致如下:
1 | VO = { |
可以看到,函数声明被整体提升,变量声明被提升但未赋值。
2️⃣ 建立作用域链(Scope Chain)
每个执行上下文都有一个作用域链,用于解析标识符(变量、函数名)。
作用域链是由当前执行上下文的 词法环境(Lexical Environment) 与所有外层环境的引用组成的一个链表结构。
简单来说:
当我们在函数中访问一个变量时,JavaScript 引擎会沿着作用域链从内到外查找,直到找到该变量或报错为止。
例如:
1 | var a = 'global'; |
inner 的作用域链如下:
1 | inner → outer → global |
当执行 console.log(a, b, c) 时,引擎会:
- 在
inner中找a,没有; - 去
outer中找a,没有; - 去
global中找到a = "global"。
3️⃣ 确定 this 指向
在执行上下文被创建时,还会确定当前 this 的绑定规则:
- 默认(非严格模式)下:
this指向全局对象; - 严格模式下:
this为undefined; - 若通过
call/apply/bind调用,则由显式绑定确定; - 若作为对象方法调用,则
this指向该对象。
四、执行阶段(Execution Phase)
创建阶段完成后,JavaScript 开始逐行执行代码,此时:
- 变量赋值;
- 函数调用;
- 表达式运算;
- 执行上下文栈的入栈与出栈操作。
五、执行上下文栈(Execution Context Stack)
为了管理多个执行上下文,JavaScript 引擎使用一个 执行上下文栈(ECS) 来维护它们的执行顺序。
栈结构示意
1 | ECStack = [ |
示例:
1 | var scope = 'global scope'; |
栈变化过程:
1️⃣ 程序启动 → GlobalEC 入栈
2️⃣ 调用 checkscope() → 创建并入栈 checkscopeEC
3️⃣ 执行 checkscope(),返回函数 f(闭包形成) → checkscopeEC 出栈,但其词法环境仍被 f 引用
4️⃣ 执行 f() → fEC 入栈,沿作用域链找到 scope = "local scope"
5️⃣ 执行完毕 → fEC 出栈,结果为 "local scope"
六、闭包与执行上下文的关系
闭包(Closure)本质上就是函数+定义时的词法环境的组合。
当一个函数在其外部作用域被返回或传递时,它依然能“记住”定义时的变量。
1 | function makeCounter() { |
这里 makeCounter 的执行上下文虽然销毁了,但它的词法环境被内部函数引用,因此 count 变量依然存在。
七、总结
| 概念 | 说明 |
|---|---|
| 执行上下文(EC) | JavaScript 代码执行时的环境 |
| 变量对象(VO) | 存储函数参数、变量声明、函数声明 |
| 作用域链(Scope Chain) | 由当前词法环境与外层环境组成的链,用于变量查找 |
| 执行上下文栈(ECS) | 管理执行顺序的栈结构 |
| 闭包(Closure) | 函数与其定义时作用域的绑定 |
八、思维导图(文字版)
1 | 执行上下文 |