掌握JavaScript执行上下文:从基础到高级

60 阅读4分钟

❓ 执行上下文(Execution Context)

执行上下文是 JavaScript 代码执行时的核心运行环境,每次函数调用或全局代码执行都会创建一个新的执行上下文,并压入调用栈(Call Stack)。执行上下文分为两个阶段:

  1. 创建阶段

    • 确定作用域链(Scope Chain)。
    • 创建变量环境(Variable Environment)和词法环境(Lexical Environment)。
    • 绑定 this 的值。
  2. 执行阶段

    • 变量赋值、函数执行、代码逐行运行。

示例:调用栈中的执行上下文

function outer() {
  let a = 1;
  function inner() {
    console.log(a);
  }
  inner();
}
outer();
  • 调用 outer() → 创建 outer 的执行上下文。
  • 调用 inner() → 创建 inner 的执行上下文,压入栈顶。
  • inner 执行完毕后弹出栈,接着 outer 弹出。

this是什么

this 是执行上下文中的一个动态属性,其值由函数调用方式决定:

调用方式this 指向示例
默认绑定非严格模式:全局对象(如 window);严格模式:undefinedfunction foo() { console.log(this); } foo();
方法调用调用该方法的对象obj.method = function() { console.log(this); }; obj.method();
构造函数新创建的实例对象function Person() { this.name = 'Alice'; } const p = new Person();
显式绑定call/apply/bind 的第一个参数func.call({ x: 1 });
箭头函数继承外层词法环境的 thisconst foo = () => { console.log(this); }; foo();

关键点

  • 箭头函数的 this 在定义时确定,无法通过 callapply 修改。
  • 回调函数(如 setTimeout)中的 this 默认指向全局对象,除非使用箭头函数或显式绑定。

📊 变量环境(Variable Environment)与词法环境(Lexical Environment)

在 ES6 之后,执行上下文中分为了两个环境来处理变量和作用域:

变量环境(Variable Environment)词法环境(Lexical Environment)
存储 var 声明的变量和函数声明。存储 letconst 声明的变量和块级作用域。
变量在创建阶段被初始化(变量提升)。变量在声明前处于“暂时性死区”(TDZ),不可访问。
作用域为函数作用域。作用域为块级作用域(如 iffor 等)。

示例:变量提升与暂时性死区

// var 的变量提升
console.log(a); // undefined
var a = 1;

// let 的暂时性死区
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;

作用域链

  • 每个词法环境都有一个 outer 引用指向外层环境,形成链式结构。
  • 变量查找时,先查找当前词法环境,再沿作用域链向外层查找。

🌍 执行上下文与环境的完整关系图解

context2.png


📝 核心角色说明

角色作用关键特性
全局对象宿主环境提供的顶级对象(如 window/global存储全局变量和函数,是全局执行上下文中 this 的默认指向。
全局执行上下文代码执行的初始环境,唯一且最先入栈包含变量环境(处理 var)和词法环境(处理 let/const),this 指向全局对象。
函数执行上下文函数调用时创建的环境,入栈执行后出栈独立的作用域链,this 由调用方式决定(默认、隐式、显式、new)。
变量环境 (VE)存储 var 声明和函数声明(变量提升)变量在创建阶段初始化为 undefined
词法环境 (LE)存储 let/const 声明和块级作用域变量在声明前处于“暂时性死区”(TDZ),不可访问。
作用域链由词法环境的 outer 引用链接而成,决定变量查找路径内部环境可访问外部环境的变量,反之不行。
执行栈管理执行上下文的调用顺序(后进先出)栈底是全局执行上下文,栈顶是当前正在执行的函数上下文。
this指向当前执行上下文所属的“所有者”动态绑定(普通函数)或静态绑定(箭头函数)。

🚨 常见问题

  1. 为什么 var 有变量提升,而 let 没有?

    • var 在变量环境中初始化值为 undefined,而 let 在词法环境中需要严格按代码顺序初始化。
  2. 如何避免 this 的指向问题?

    • 使用箭头函数、显式绑定(bind)、或在方法内保存 const self = this;
  3. 闭包是如何形成的?

    • 函数保留了对外部词法环境的引用,即使外部函数已执行完毕。