执行上下文

123 阅读4分钟

一、执行上下文的核心概念

1. 定义

执行上下文(Execution Context)是 JavaScript 引擎对代码进行解析和执行时创建的环境,它定义了变量、函数和 this 的访问规则。

2. 作用

  • 确定代码中变量和函数的作用域;
  • 管理 this 的指向;
  • 控制代码的执行顺序。

二、执行上下文的类型

类型触发场景核心特性
全局上下文浏览器加载 JavaScript 文件时创建- 包含全局对象(如 window)
- 定义全局变量和函数
- 仅存在一个
函数上下文函数被调用时创建- 每个函数调用对应独立上下文
- 包含 arguments 对象
- 支持闭包
eval 上下文使用 eval() 执行字符串代码时创建- 已较少使用,存在安全风险

三、执行上下文的创建过程

以函数上下文为例,其创建分为两个阶段:编译阶段执行阶段

1. 编译阶段:创建执行上下文(EC)
function example(a) {
  var b = 2;
  function c() {}
  let d = 3;
}

example(1);

执行上下文创建步骤:

  • 1. 确定 this 指向

    • 函数调用时:this 指向调用者(如 obj.example()this 指向 obj);
    • 普通函数调用:默认指向全局对象(浏览器中为 window);
    • 箭头函数:继承外层上下文的 this
  • 2. 生成变量环境(Variable Environment)

    • 变量提升:var 声明的变量、函数声明会被提升到作用域顶部,初始值为 undefined
    • let/const 暂时性死区:let/const 声明的变量在声明前无法访问(TDZ 机制)。
  • 3. 生成词法环境(Lexical Environment)

    • 存储函数的参数、变量和 inner 函数;
    • 包含指向外层词法环境的引用(形成作用域链)。
2. 执行阶段:执行代码
  • 按顺序执行代码,为变量赋值,调用函数;
  • 函数执行完毕后,其执行上下文从调用栈中弹出(除非存在闭包引用)。

四、执行上下文栈(调用栈)

1. 定义

  • 用于管理执行上下文的栈结构,遵循「后进先出(LIFO)」原则。

2. 工作流程

// 示例代码
function a() {
  console.log('a 开始');
  b();
  console.log('a 结束');
}

function b() {
  console.log('b 执行');
}

// 执行过程
console.log('全局开始');
a();
console.log('全局结束');

调用栈变化:

  1. 全局上下文入栈 → [全局上下文]
  2. 执行 console.log('全局开始')
  3. 调用 a(),a 函数上下文入栈 → [全局上下文, a 上下文]
  4. 执行 console.log('a 开始')
  5. 调用 b(),b 函数上下文入栈 → [全局上下文, a 上下文, b 上下文]
  6. 执行 console.log('b 执行'),b 函数执行完毕,出栈 → [全局上下文, a 上下文]
  7. 执行 console.log('a 结束'),a 函数执行完毕,出栈 → [全局上下文]
  8. 执行 console.log('全局结束'),全局上下文在页面关闭时出栈

五、经典案例:闭包与执行上下文

function outer() {
  var count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}

const counter = outer();
counter(); // 1
counter(); // 2

执行上下文分析:

  1. 调用 outer() 时,创建 outer 上下文,入栈;
  2. outer 执行完毕后返回 inner 函数,其上下文出栈,但 inner 引用了 outer 中的 count
  3. 由于闭包存在,outer 上下文中的 count 被保留在内存中;
  4. 每次调用 counter()(即 inner 函数)时,创建 inner 上下文,通过作用域链访问 outer 的 count,实现累加。

六、问题

1. 问:变量提升和暂时性死区的区别?

    • 变量提升:var 声明的变量和函数声明会被提升到作用域顶部,初始值为 undefined
    • 暂时性死区:let/const 声明的变量在声明前处于 TDZ,访问会报错(ReferenceError)。

2. 问:执行上下文和作用域的关系?

    • 执行上下文是代码执行时的环境,包含作用域信息;
    • 作用域是变量的访问规则,由词法环境中的作用域链决定。

3. 问:为什么递归函数可能导致栈溢出?

    • 每次递归调用都会创建新的函数上下文入栈;
    • 若递归深度过大,调用栈超出内存限制,会抛出 RangeError: Maximum call stack size exceeded

总结

执行上下文是 JavaScript 引擎的核心机制,它定义了代码执行时的变量访问、this 指向和作用域规则。理解执行上下文的创建过程、调用栈机制和闭包对上下文的影响,是掌握 JavaScript 异步编程、作用域链和性能优化的基础。在实际开发中,合理管理执行上下文(如避免过深的递归、正确处理闭包)能有效避免内存泄漏和栈溢出问题。