执行上下文与作用域链

理解 JavaScript 中执行上下文的创建过程和作用域链的查找机制

问题

解释 JavaScript 中的执行上下文(全局、函数、块级)和作用域链是如何工作的。

解答

执行上下文

执行上下文是 JavaScript 代码执行时的环境,包含变量、函数声明、this 等信息。

// 1. 全局执行上下文 - 程序启动时创建
var globalVar = 'global';

function outer() {
  // 2. 函数执行上下文 - 函数调用时创建
  var outerVar = 'outer';
  
  function inner() {
    // 3. 另一个函数执行上下文
    var innerVar = 'inner';
    console.log(innerVar, outerVar, globalVar);
  }
  
  inner();
}

outer(); // 输出: inner outer global

执行上下文的组成

// 执行上下文包含三个部分
const ExecutionContext = {
  // 1. 变量环境 - 存储 var 声明和函数声明
  VariableEnvironment: {},
  
  // 2. 词法环境 - 存储 let/const 声明
  LexicalEnvironment: {},
  
  // 3. this 绑定
  ThisBinding: undefined
};

块级作用域

function blockScopeDemo() {
  var a = 1;      // 函数作用域
  let b = 2;      // 块级作用域
  const c = 3;    // 块级作用域
  
  if (true) {
    var a = 10;   // 同一个 a,被覆盖
    let b = 20;   // 新的 b,只在 if 块内有效
    const c = 30; // 新的 c,只在 if 块内有效
    
    console.log(a, b, c); // 10 20 30
  }
  
  console.log(a, b, c); // 10 2 3
}

blockScopeDemo();

作用域链

作用域链在函数定义时确定,而非调用时。

var x = 10;

function foo() {
  console.log(x); // 沿作用域链查找 x
}

function bar() {
  var x = 20;
  foo(); // 输出 10,不是 20
}

bar();

// foo 的作用域链: foo -> 全局
// 在 foo 定义时就确定了,与调用位置无关

作用域链查找过程

var a = 'global a';

function outer() {
  var b = 'outer b';
  
  function middle() {
    var c = 'middle c';
    
    function inner() {
      var d = 'inner d';
      
      // 查找顺序: inner -> middle -> outer -> global
      console.log(d); // inner d (当前作用域找到)
      console.log(c); // middle c (上一层找到)
      console.log(b); // outer b (再上一层找到)
      console.log(a); // global a (全局作用域找到)
    }
    
    inner();
  }
  
  middle();
}

outer();

闭包与作用域链

function createCounter() {
  let count = 0; // 被闭包捕获
  
  return {
    increment() {
      count++;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2

// increment 和 getCount 的作用域链中保留了对 count 的引用

执行上下文栈

function first() {
  console.log('first start');
  second();
  console.log('first end');
}

function second() {
  console.log('second start');
  third();
  console.log('second end');
}

function third() {
  console.log('third');
}

first();

// 执行上下文栈变化:
// 1. [Global]
// 2. [Global, first]
// 3. [Global, first, second]
// 4. [Global, first, second, third]
// 5. [Global, first, second]  <- third 执行完出栈
// 6. [Global, first]          <- second 执行完出栈
// 7. [Global]                 <- first 执行完出栈

关键点

  • 执行上下文分三种:全局、函数、eval(块级作用域不创建新的执行上下文)
  • 作用域链在函数定义时确定,不是调用时(词法作用域)
  • var 是函数作用域,let/const 是块级作用域
  • 变量查找沿作用域链从内向外,找到即停止
  • 闭包能访问外部变量,是因为作用域链的引用被保留