JavaScript 执行上下文深度解析

215 阅读5分钟

目录

  1. 执行上下文类型

  2. 变量环境 vs 词法环境

  3. 外部环境引用与作用域链


1. 执行上下文类型

JavaScript 代码执行时,会创建三种类型的执行上下文:

1.1 全局执行上下文

  • 特点:最顶层的执行环境,唯一且全局唯一。
  • 创建时机:脚本首次加载时自动创建。
  • 全局对象:浏览器中为 window,Node.js 中为 global
  • 生命周期:伴随程序运行始终,直到页面关闭或进程退出。
var globalVar = "全局变量";
console.log(window.globalVar); // "全局变量"(浏览器环境)

1.2 函数执行上下文

  • 特点:每次函数调用时动态创建。
  • 创建时机:函数被调用时(即使递归调用也会新建)。
  • 生命周期:函数执行完毕后销毁(闭包除外)。
function func() {
  var localVar = "局部变量";
  console.log(localVar); // "局部变量"
}
func();
// console.log(localVar); // 报错:localVar未定义

1.3 Eval 执行上下文

  • 特点eval 函数内部代码的执行环境(严格模式下独立作用域)。
  • 创建时机eval 函数执行时。
  • 使用场景:极少使用,存在安全性和性能问题。
eval('var evalVar = "eval变量";');
console.log(evalVar); // "eval变量"(非严格模式)

2. 变量环境 vs 词法环境

2.1 变量环境(Variable Environment)

  • 存储内容var 声明的变量和函数声明。
  • 变量提升:声明在编译阶段被提升并初始化为 undefined
  • 作用域特性:函数作用域。
console.log(a); // undefined(变量提升)
var a = 10;

2.2 词法环境(Lexical Environment)

  • 存储内容let/const 声明的变量、块级作用域、this 绑定。
  • 暂时性死区(TDZ) :声明前访问会报错。
  • 作用域特性:块级作用域(ES6+)。
{
  console.log(b); // 报错:Cannot access 'b' before initialization
  let b = 20;
}

2.3 对比与差异

特性变量环境(var词法环境(let/const
作用域函数作用域块级作用域
提升行为声明提升并初始化为 undefined提升但不初始化(TDZ)
重复声明允许禁止
全局对象绑定是(var 在全局作用域)否(let/const 不在全局对象上)

3. 外部环境引用与作用域链

3.1 外部环境引用(Outer Reference)

  • 定义:指向父级词法环境的指针(由函数声明时的位置决定)。
  • 静态作用域:JavaScript 使用词法作用域(函数定义时决定作用域链)。
function outer() {
  let outerVar = "outer";
  function inner() {
    console.log(outerVar); // 通过外部环境引用访问 outerVar
  }
  inner();
}
outer();

3.2 作用域链形成机制

  1. 创建阶段:执行上下文初始化时,根据函数定义位置建立外部环境引用。
  2. 链式结构:每个词法环境通过 [[OuterEnv]] 链接到父环境,形成作用域链。
  3. 变量查找:沿作用域链逐级向上查找变量,直到全局环境。

3.3 作用域链查找示例

let globalVal = "global";
function parent() {
  let parentVal = "parent";
  function child() {
    let childVal = "child";
    console.log(childVal);    // "child"(当前词法环境)
    console.log(parentVal);  // "parent"(父级词法环境)
    console.log(globalVal);  // "global"(全局词法环境)
  }
  child();
}
parent();

作用域链结构

child 词法环境 --> parent 词法环境 --> 全局词法环境 --> null

关键点

  • 闭包通过保留外部环境引用实现跨作用域访问。
  • 作用域链在函数定义时静态确定,与调用位置无关。

通过理解执行上下文、环境差异和作用域链机制,可以更清晰地掌握 JavaScript 的变量查找、闭包行为及作用域规则。