[2-2] 执行上下文与闭包 · 作用域与作用域链(Scope & Scope Chain)

8 阅读3分钟

所属板块:2. 执行上下文与闭包(JS 的核心引擎)

记录日期:2026-03-xx
更新:遇到作用域链或变量查找相关输出题时补充

1. 作用域:变量的“合法活动范围”

作用域就是 JS 引擎规定“这个变量在哪一段代码里可以被访问”的边界规则。
如果把 [2-1] 里的执行上下文比作“容器”,那么作用域就是容器内部给每个变量划定的“活动区域”。

JS 采用的是词法作用域(Lexical Scope / 静态作用域),核心特点:
一个变量的作用域在代码书写(定义)的那一刻就已经确定,跟函数在哪里被调用完全无关。这和 this 的“调用时动态绑定”形成鲜明对比,是 JS 引擎最稳定的查找法则。

(注意:JS 没有动态作用域,只有极少数老式语言如 Bash、Perl 才用动态作用域。)

2. JS 的三种作用域类型

  1. 全局作用域

    • 最外层,变量默认挂在 window / global 上
    • 容易造成污染,生产代码中应尽量避免
  2. 函数作用域(ES5 及之前的主力)

    • 每个 function {} 形成独立作用域
    • var 声明的变量只有函数作用域(没有块的概念)
  3. 块级作用域(ES6 新增)

    • {} 配合 let / const 形成
    • if、for、while、try-catch、单独的 {} 都能创建块级作用域
    • 彻底解决了 var 的变量提升泄漏和全局污染问题

示例对比(经典 for 循环题):

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);   // 输出 3 3 3(var 是函数作用域)
}

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0);   // 输出 0 1 2(let 是块级作用域)
}

3. 作用域链(Scope Chain):变量查找的“导航链表”

当引擎需要读取或写入一个变量时,会按以下路径查找:

  1. 先在当前执行上下文的变量对象里找
  2. 找不到 → 顺着作用域链(每个上下文持有的 [[outer]] 指针)去外层上下文查找
  3. 一直找到全局作用域
  4. 全局还找不到 → 抛 ReferenceError

作用域链在 [2-1] 创建阶段就已经连好,不会因为函数调用位置而改变(词法作用域的体现)。

示例(多层嵌套 + 变量遮蔽):

var x = "global";

function outer() {
  var x = "outer";

  function inner() {
    var x = "inner";
    console.log(x);     // "inner"(就近原则,先找自己的作用域)
  }

  inner();
}

outer();

4. LHS 查询 vs RHS 查询(引擎底层查找方式)

这是《你不知道的 JavaScript》里非常核心的概念,面试常考:

  • RHS 查询(Right-Hand Side):取值操作(读取变量)
    示例:console.log(a) → 查找 a 的值

  • LHS 查询(Left-Hand Side):赋值操作(写入变量)
    示例:a = 2 → 查找 a 的位置准备赋值

如果 LHS 查询最终在全局都找不到,且不是严格模式,会自动创建全局变量(这是 var 的隐蔽 bug 根源之一)。

5. 小结 & 复习时的“引擎视角”

  • 记住核心法则:作用域在定义时就定死了,顺着作用域链从内向外找
  • 遇到“变量找不到”或“拿到的值不对”时,画一下作用域链(从当前函数 → 外层函数 → 全局)
  • 优先使用 let / const 开启块级作用域,能大幅减少隐蔽 bug
  • [2-1] 的执行上下文 + 本文的 作用域链,共同构成了后面闭包的底层基础

下一篇文章会进入 [2-3] 动态上下文 this 指向机制(五大绑定规则、手写 call/apply/bind 等)。

返回总目录:戳这里