执行上下文(Execution Context)

JavaScript 引擎执行代码时的底层机制:执行上下文的创建、变量环境、词法环境


1. 执行上下文概述

1.1 什么是执行上下文

定义:执行上下文是 JavaScript 引擎执行代码时的环境,包含了执行代码所需的所有信息。

类型

  • 全局执行上下文(Global Execution Context):代码首次执行时创建
  • 函数执行上下文(Function Execution Context):函数调用时创建
  • Eval 执行上下文(Eval Execution Context):eval 函数执行时创建(不推荐使用)

1.2 JavaScript 代码的执行流程

重要概念:一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成之后,才会进入执行阶段。

执行流程

JavaScript 代码
    ↓
[编译阶段]
    ↓
执行上下文 + 可执行代码
    ↓
[执行阶段]
    ↓
执行代码,输出结果

详细流程

  1. 编译阶段

    • 词法分析(Lexical Analysis)
    • 语法分析(Parsing)
    • 生成 AST(Abstract Syntax Tree)
    • 创建执行上下文
    • 处理声明(变量提升)
  2. 执行阶段

    • 执行可执行代码
    • 变量赋值
    • 函数调用
    • 表达式求值

1.3 执行上下文的结构

根据 ECMAScript 规范,执行上下文包含以下组件:

ExecutionContext {
  LexicalEnvironment: {...},      // 词法环境
  VariableEnvironment: {...},     // 变量环境
  PrivateEnvironment: {...},      // 私有环境(ES2022,class 私有字段)
  ThisBinding: value,             // this 绑定
  Function: function,             // 函数对象(仅函数执行上下文)
  Realm: realm,                   // Realm(隔离环境)
  ScriptOrModule: module          // 脚本或模块(ES6+)
}

2. 变量环境(Variable Environment)

2.1 变量环境的定义

定义:变量环境(Variable Environment)是执行上下文中存储 varfunction 声明的地方。

特点

  • 存储 var 声明的变量
  • 存储 function 声明(ES5 及之前)
  • 在创建阶段初始化为 undefined
  • 是执行上下文的一个组件

2.2 var 声明的处理过程

2.2.1 编译阶段(Compilation Phase)

JavaScript 引擎的处理流程

var x = 10;
var y = 20;

引擎处理步骤

  1. 词法分析(Lexical Analysis)

    • 识别 varx=10 等 tokens
  2. 语法分析(Parsing)

    • 构建抽象语法树(AST)
    • 识别变量声明语句
  3. 作用域分析(Scope Analysis)

    • 确定变量属于哪个作用域
    • 对于 var,属于函数作用域或全局作用域
  4. 创建变量环境(Variable Environment Creation)

    • 在变量环境中创建绑定
    • 初始化为 undefined

伪代码表示

// 编译阶段
VariableEnvironment = {
  x: undefined,  // var 声明,初始化为 undefined
  y: undefined   // var 声明,初始化为 undefined
};

2.2.2 执行阶段(Execution Phase)

变量赋值过程

// 执行阶段
x = 10;  // 在变量环境中查找 x,赋值
y = 20;  // 在变量环境中查找 y,赋值

执行后的变量环境

VariableEnvironment = {
  x: 10,
  y: 20
};

2.3 全局 var 声明与全局对象

2.3.1 全局对象(Global Object)

浏览器环境window 对象 Node.js 环境global 对象 ES2020+globalThis(统一访问)


2.3.2 var 如何添加到全局对象

关键机制:全局执行上下文中的变量环境就是全局对象本身。

ECMAScript 规范

  • 全局执行上下文的变量环境 = 全局对象(Global Object)
  • var 在全局作用域声明时,会直接在全局对象上创建属性

示例

var globalVar = 10;
console.log(window.globalVar);  // 10(浏览器)
console.log(globalVar);         // 10
console.log(this.globalVar);    // 10(全局 this 指向 window)

引擎内部实现(简化)

// 全局执行上下文创建
GlobalExecutionContext = {
  VariableEnvironment: globalThis,  // 变量环境 = 全局对象
  LexicalEnvironment: {
    // 词法环境(存储 let/const)
    outer: null
  },
  ThisBinding: globalThis
};
 
// var 声明时
globalThis.globalVar = undefined;  // 创建阶段
globalThis.globalVar = 10;         // 执行阶段

2.3.3 let/const 为什么不会添加到全局对象

关键区别

  • var 存储在变量环境(Variable Environment)= 全局对象
  • let/const 存储在词法环境(Lexical Environment)≠ 全局对象

示例

var varVar = 1;
let letVar = 2;
const constVar = 3;
 
console.log(window.varVar);    // 1 ✅
console.log(window.letVar);    // undefined ❌
console.log(window.constVar);  // undefined ❌

引擎内部实现(简化)

GlobalExecutionContext = {
  VariableEnvironment: globalThis,  // var 存储在这里
  LexicalEnvironment: {             // let/const 存储在这里
    letVar: <uninitialized>,        // TDZ
    constVar: <uninitialized>,      // TDZ
    outer: null
  }
};
 
// var 声明
globalThis.varVar = 1;  // 直接添加到全局对象
 
// let/const 声明
LexicalEnvironment.letVar = 2;  // 存储在词法环境,不添加到全局对象

3. 词法环境(Lexical Environment)

3.1 词法环境的定义

定义:词法环境(Lexical Environment)是 ECMAScript 规范中定义的环境记录(Environment Record),用于存储标识符绑定。

结构

LexicalEnvironment {
  EnvironmentRecord: {...},  // 环境记录
  OuterLexicalEnvironment: reference  // 外部词法环境引用
}

3.2 环境记录(Environment Record)

类型

  1. 声明式环境记录(Declarative Environment Record)

    • 存储 letconstclassmodule 等声明
    • 存储函数声明(ES6+)
    • 存储 catch 子句中的变量
  2. 对象环境记录(Object Environment Record)

    • 将环境记录与对象关联
    • 用于 with 语句和全局对象
  3. 函数环境记录(Function Environment Record)

    • 函数执行上下文使用
    • 包含 this 绑定、arguments 对象
  4. 模块环境记录(Module Environment Record)

    • ES6 模块使用
    • 支持 import/export

3.3 词法环境的创建过程

3.3.1 全局词法环境

创建过程

// 全局代码
let x = 10;
const y = 20;

引擎内部实现(简化)

// 全局执行上下文创建
GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      x: <uninitialized>,  // TDZ
      y: <uninitialized>   // TDZ
    },
    OuterLexicalEnvironment: null  // 全局词法环境没有外部引用
  },
  VariableEnvironment: globalThis
};
 
// 执行阶段
LexicalEnvironment.EnvironmentRecord.x = 10;
LexicalEnvironment.EnvironmentRecord.y = 20;

3.3.2 函数词法环境

创建过程

function test(a, b) {
  let x = 10;
  const y = 20;
  var z = 30;
}

引擎内部实现(简化)

// 函数执行上下文创建
FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      // 函数环境记录
      ThisBinding: <function>,
      Arguments: { a: 1, b: 2 },
      x: <uninitialized>,  // let 声明,TDZ
      y: <uninitialized>   // const 声明,TDZ
    },
    OuterLexicalEnvironment: <GlobalLexicalEnvironment>  // 外部词法环境
  },
  VariableEnvironment: {
    z: undefined  // var 声明,初始化为 undefined
  }
};
 
// 执行阶段
LexicalEnvironment.EnvironmentRecord.x = 10;
LexicalEnvironment.EnvironmentRecord.y = 20;
VariableEnvironment.z = 30;

3.4 块级词法环境

块级作用域的实现

{
  let blockVar = 10;
  const blockConst = 20;
}

引擎内部实现(简化)

// 块级作用域创建新的词法环境
BlockLexicalEnvironment = {
  EnvironmentRecord: {
    blockVar: <uninitialized>,
    blockConst: <uninitialized>
  },
  OuterLexicalEnvironment: <父级词法环境>
};
 
// 执行后,块级词法环境可能被销毁(取决于闭包)

4. 变量环境 vs 词法环境

4.1 区别对比

特性变量环境(Variable Environment)词法环境(Lexical Environment)
存储内容var 声明、function 声明(ES5)letconstclassfunction(ES6+)
初始化undefined<uninitialized>(TDZ)
作用域函数作用域/全局作用域块级作用域
提升行为提升并初始化为 undefined提升但进入 TDZ
全局对象var 会添加到全局对象let/const 不会添加到全局对象

4.2 为什么需要两个环境

历史原因

  • ES5 之前只有 varfunction,使用变量环境
  • ES6 引入 let/const,需要新的环境记录机制
  • 为了向后兼容,保留变量环境,新增词法环境

技术原因

  • 变量环境用于处理函数作用域的变量
  • 词法环境用于处理块级作用域的变量
  • 两者分离可以更精确地控制变量的生命周期

5. 执行上下文的生命周期

5.1 创建阶段(Creation Phase)

步骤

  1. 创建词法环境(Lexical Environment)

    LexicalEnvironment = {
      EnvironmentRecord: {...},
      OuterLexicalEnvironment: <outer reference>
    };
  2. 创建变量环境(Variable Environment)

    VariableEnvironment = {
      // 对于全局执行上下文,等于全局对象
      // 对于函数执行上下文,创建新的环境记录
    };
  3. 确定 this 绑定(This Binding)

    ThisBinding = <determined by call>;
  4. 处理声明(Declaration Processing)

    • var 声明:在变量环境中创建,初始化为 undefined
    • function 声明:在变量环境中创建,初始化为函数对象
    • let/const 声明:在词法环境中创建,进入 TDZ

5.2 执行阶段(Execution Phase)

步骤

  1. 变量赋值

    // 在变量环境或词法环境中查找变量
    // 执行赋值操作
  2. 代码执行

    // 逐行执行代码
    // 处理函数调用、表达式求值等

5.3 销毁阶段(Destruction Phase)

步骤

  1. 判断是否可以销毁

    • 如果没有闭包引用,可以销毁
    • 如果有闭包引用,保留词法环境
  2. 内存回收

    • 标记为可回收
    • 等待垃圾回收器回收

6. 实际示例分析

6.1 全局执行上下文分析

代码

var globalVar = 10;
let globalLet = 20;
const globalConst = 30;
 
function test() {
  console.log(globalVar, globalLet, globalConst);
}

执行上下文结构

GlobalExecutionContext = {
  // 词法环境
  LexicalEnvironment: {
    EnvironmentRecord: {
      globalLet: 20,
      globalConst: 30,
      test: <function object>
    },
    OuterLexicalEnvironment: null
  },
  // 变量环境 = 全局对象
  VariableEnvironment: globalThis,  // 即 window (浏览器) 或 global (Node.js)
  // globalThis.globalVar = 10  (var 声明在这里)
  ThisBinding: globalThis
};

验证

console.log(window.globalVar);    // 10 ✅
console.log(window.globalLet);    // undefined ❌
console.log(window.globalConst);  // undefined ❌

6.2 函数执行上下文分析

代码

function outer(a, b) {
  var varVar = 1;
  let letVar = 2;
  const constVar = 3;
  
  function inner() {
    console.log(varVar, letVar, constVar);
  }
  
  return inner;
}
 
let func = outer(10, 20);
func();

执行上下文创建过程

1. outer 函数执行上下文创建

FunctionExecutionContext(outer) = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      ThisBinding: undefined,
      Arguments: { a: 10, b: 20 },
      letVar: <uninitialized>,
      constVar: <uninitialized>,
      inner: <function object>
    },
    OuterLexicalEnvironment: <GlobalLexicalEnvironment>
  },
  VariableEnvironment: {
    varVar: undefined,
    inner: <function object>
  }
};

2. outer 函数执行

// 执行阶段
VariableEnvironment.varVar = 1;
LexicalEnvironment.EnvironmentRecord.letVar = 2;
LexicalEnvironment.EnvironmentRecord.constVar = 3;

3. inner 函数执行上下文创建(闭包)

FunctionExecutionContext(inner) = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      ThisBinding: undefined,
      Arguments: {}
    },
    OuterLexicalEnvironment: <outer的LexicalEnvironment>  // 保留引用!
  },
  VariableEnvironment: {
    // 空
  }
};

关键:inner 的外部词法环境引用指向 outer 的词法环境,形成闭包!


7. 变量提升(Hoisting)

7.1 什么是变量提升

定义:变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的”行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined

重要说明

  • 变量和函数声明在代码里的位置是不会改变的
  • 变量提升实际上是在编译阶段被 JavaScript 引擎放入内存中
  • 对,你没听错,一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译

7.2 变量提升的示例

代码

showName()
console.log(myname)
var myname = '极客时间'
function showName() {
  console.log('函数showName被执行')
}

执行结果

函数showName被执行
undefined

分析

  • 第1行输出”函数showName被执行”,说明函数可以在定义之前使用
  • 第2行输出”undefined”,说明变量可以在定义之前使用,但值是 undefined

7.3 变量提升的底层机制

编译阶段

// 原始代码
showName()
console.log(myname)
var myname = '极客时间'
function showName() {
  console.log('函数showName被执行')
}
 
// 编译阶段处理后的结果
// 第一部分:变量提升部分
var myname = undefined
function showName() {
  console.log('函数showName被执行')
}
 
// 第二部分:可执行代码部分
showName()
console.log(myname)
myname = '极客时间'  // 去掉 var 声明,保留赋值语句

执行上下文创建

// 编译阶段生成执行上下文
ExecutionContext = {
  VariableEnvironment: {
    myname: undefined,  // var 声明,初始化为 undefined
    showName: <function object>  // function 声明,初始化为函数对象
  },
  LexicalEnvironment: {...},
  ThisBinding: window
}

执行阶段

// 1. 执行 showName()
// JavaScript 引擎在 VariableEnvironment 中查找 showName
// 找到函数对象,执行函数,输出 "函数showName被执行"
 
// 2. 执行 console.log(myname)
// JavaScript 引擎在 VariableEnvironment 中查找 myname
// 找到 myname,值为 undefined,输出 undefined
 
// 3. 执行 myname = '极客时间'
// 在 VariableEnvironment 中查找 myname,赋值为 '极客时间'

7.4 var 的提升机制

代码

console.log(x);
var x = 10;

编译阶段

// 创建执行上下文
ExecutionContext = {
  VariableEnvironment: {
    x: undefined  // var 声明,初始化为 undefined
  }
}

执行阶段

// 1. console.log(x)
// 在 VariableEnvironment 中查找 x,值为 undefined
console.log(VariableEnvironment.x);  // undefined
 
// 2. x = 10
// 在 VariableEnvironment 中查找 x,赋值为 10
VariableEnvironment.x = 10;

7.5 let/const 的 TDZ(暂时性死区)

代码

console.log(y);
let y = 20;

编译阶段

// 创建执行上下文
ExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      y: <uninitialized>  // let 声明,进入 TDZ
    },
    OuterLexicalEnvironment: null
  },
  VariableEnvironment: {...}
}

执行阶段

// 1. console.log(y)
// 在 LexicalEnvironment 中查找 y
// y 处于 <uninitialized> 状态,仍在 TDZ 中
// ❌ ReferenceError: Cannot access 'y' before initialization

TDZ 的特点

  • let/const 声明也会被提升,但不会初始化为 undefined
  • 在声明之前访问变量会抛出 ReferenceError
  • 这被称为”暂时性死区”(Temporal Dead Zone,简称 TDZ)

7.6 函数提升

代码

showName()
function showName() {
  console.log('函数showName被执行')
}

执行结果

函数showName被执行

分析

  • 函数声明会被提升到代码开头
  • 函数声明会被初始化为函数对象
  • 可以在函数声明之前调用函数

编译阶段

// 创建执行上下文
ExecutionContext = {
  VariableEnvironment: {
    showName: <function object>  // function 声明,初始化为函数对象
  }
}

7.7 相同名称的变量和函数

代码

function showName() {
  console.log('极客邦')
}
showName()
function showName() {
  console.log('极客时间')
}
showName()

执行结果

极客时间
极客时间

分析

  • 在编译阶段,遇到第一个 showName 函数,会将该函数体存放到变量环境中
  • 接下来是第二个 showName 函数,继续存放至变量环境中
  • 但是变量环境中已经存在一个 showName 函数了,此时,第二个 showName 函数会将第一个 showName 函数覆盖掉
  • 这样变量环境中就只存在第二个 showName 函数了

结论:一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数。


7.8 函数表达式不会被提升

代码

showName()
var showName = function() {
  console.log(2)
}
function showName() {
  console.log(1)
}

执行结果

1

分析

  • var showName 会被提升,初始化为 undefined
  • function showName() 会被提升,初始化为函数对象
  • 函数声明会覆盖变量声明(但变量仍然存在)
  • 执行 showName() 时,调用的是函数声明
  • 执行 showName = function() {...} 时,会覆盖函数声明

编译阶段

// 创建执行上下文
ExecutionContext = {
  VariableEnvironment: {
    showName: <function object>  // function 声明优先
  }
}

执行阶段

// 1. showName() 输出 1
// 2. showName = function() {...} 覆盖函数声明
// 如果后面再有 showName 执行的话,就输出 2,因为这时候函数引用已经变了

8. 参考资源


javascript 执行上下文 词法环境 变量环境 底层原理