JavaScript 执行上下文、作用域与词法环境详解

113 阅读5分钟

一、执行上下文(Execution Context)

定义
执行上下文是 JavaScript 代码运行的动态环境,包含代码执行所需的所有信息。每当代码运行时,会创建对应的执行上下文,主要分为三类:

  1. 全局执行上下文

    • 代码首次运行时默认创建。
    • 生命周期与程序一致。
    • 包含全局对象(如浏览器中的 window)和 this 指向全局对象。
  2. 函数执行上下文

    • 每次函数调用时创建,函数执行完毕后销毁。
    • 每个函数调用都会生成独立的上下文。
  3. Eval 执行上下文

    • 仅在使用 eval() 时创建(不推荐使用)。

执行上下文的组成

每个执行上下文包含三个核心组件:

  1. 词法环境(Lexical Environment)

    • 记录变量、函数声明和外部引用(作用域链)。
    LexicalEnvironment = {
      EnvironmentRecord: { /* let/const/函数声明 */ },
      outer: <父级词法环境引用>
    }
    
  2. 变量环境(Variable Environment)

    • 专门存储 var 声明的变量(处理变量提升)。
    VariableEnvironment = {
      EnvironmentRecord: { /* var声明 */ },
      outer: <父级引用>
    }
    
  3. this 绑定

    • 指向当前执行环境的上下文对象。

二、词法环境(Lexical Environment)

定义
词法环境是执行上下文的核心组件,用于存储变量和函数声明,并基于代码的词法结构(静态作用域)确定作用域层级。

结构:
  1. 环境记录(Environment Record)

    • 声明式环境记录:存储变量、函数、参数(如函数内部)。
    • 对象式环境记录:用于全局或 with 语句(以对象形式存储)。
  2. 外部引用(Outer Reference)

    • 指向父级词法环境,形成作用域链。
特点:
  • 块级环境{} 包裹的代码块(如 iffor)会创建新的词法环境。

三、变量环境(Variable Environment)

定义
变量环境是词法环境的一部分,专门处理 var 声明的变量,实现变量提升等特性。

与词法环境的区别:
特性词法环境变量环境
存储内容letconst、函数声明var 声明
作用域规则块级作用域函数/全局作用域
变量提升无(存在暂时性死区)变量提升(初始化为 undefined

四、作用域(Scope)

定义
作用域是变量和函数的可访问范围,由代码的静态结构决定。JavaScript 支持三种作用域:

  1. 全局作用域

    • 在全局执行上下文中定义,任何位置均可访问。
  2. 函数作用域

    • 函数内部定义的变量,仅函数内可访问。
  3. 块级作用域(ES6+)

    • 由 {} 包裹的代码块限定,仅 let 和 const 声明的变量生效。

作用域链(Scope Chain)
  • 机制:当访问变量时,引擎从当前词法环境开始查找,沿外部引用逐层向外搜索。
  • 静态性:作用域链在代码定义时确定,不受运行时调用位置影响。

五、核心机制解析

1. 执行上下文的生命周期
  1. 创建阶段

    • 初始化词法环境和变量环境。

    • 处理声明(变量提升):

      • var 变量初始化为 undefined
      • let/const 变量不初始化(进入暂时性死区)。
      • 函数声明完全提升。
    • 确定 this 指向。

  2. 执行阶段

    • 逐行执行代码,完成变量赋值和函数调用。

2. 块级作用域与词法环境
  • 块级代码(如 iffor)不会创建新的执行上下文,但会生成块级词法环境
  • 动态性:块级词法环境在执行到代码块时动态创建,结束后销毁。

块级作用域内的变量创建也分为两种情况,通过var创建的变量在函数或者全局执行上下文创建阶段被创建,而let const定义的变量则会在代码执行到该块级代码时动态创建,这些动态创建的变量被存储在一个“块级的词法环境”中,多个块级作用域在词法环境中维护在一个类似栈的结构中,块代码执行完毕后,块作用域(词法环境)随即被移除

示例:for 循环中的块级变量
for (let i = 0; i < 3; i++) {
  console.log(i); // 输出 0, 1, 2(每次迭代生成独立的块级环境)
}
console.log(i); // 报错:i 未定义

3. 变量查找机制
  • 步骤

    1. 当前词法环境 → 2. 外部引用指向的父级环境 → 3. 全局环境。
  • var 的特殊性
    var 变量存储在变量环境中,但通过作用域链仍可被查找到。

示例:作用域链查找
function outer() {
  var a = "outer";
  function inner() {
    console.log(a); // 查找路径:inner词法环境 → outer词法环境(变量环境)
  }
  inner();
}
outer(); // 输出 "outer"

六、关键区别与联系

维度执行上下文作用域
定义动态的运行时环境(函数调用时创建)静态的变量访问规则(代码定义时确定)
组成包含词法环境、变量环境、this由词法环境的作用域链实现
生命周期随代码执行动态创建和销毁在代码定义时固定不变
核心目标管理代码执行所需的环境信息确定变量和函数的可访问性

七、综合示例解析

let globalVar = "Global";

function outer() {
  var outerVar = "Outer";
  
  if (true) {
    let blockVar = "Block";
    console.log(globalVar); // 查找路径:块级环境 → outer环境 → 全局环境
  }

  function inner() {
    console.log(outerVar); // 查找路径:inner环境 → outer环境(变量环境)
  }
  inner();
}

outer();
执行流程:
  1. 全局上下文创建

    • 词法环境记录 globalVar 和 outer 函数。
  2. 调用 outer()

    • 创建 outer 的执行上下文,变量环境记录 outerVar,词法环境记录 inner 函数。
  3. 执行 if 块

    • 创建块级词法环境,记录 blockVar
  4. 调用 inner()

    • 创建 inner 的执行上下文,通过作用域链查找到 outerVar

八、总结

  • 执行上下文是动态的运行时环境,管理代码执行细节。
  • 词法环境变量环境共同存储变量,通过作用域链实现查找。
  • 作用域是静态规则,由代码结构决定变量可访问性。
  • 块级作用域通过动态创建词法环境实现,限制 let/const 变量的访问范围。