JS筑基(一)-关于作用域与闭包

39 阅读4分钟

JavaScript 执行环境、调用栈与作用域链


一、调用栈与执行环境

调用栈(Call Stack)

  • 是 JavaScript 引擎用于管理函数执行顺序的结构。
  • 先进后出:最后进入栈的函数最先被执行完并弹出。
let name = "Mike";
function fn() {
  let name = "Alan";
  fn2();
  function fn2() {
    console.log(name);
  }
}
fn(); // Alan

执行顺序(调用栈):

全局环境 -> fn() -> fn2()

执行环境(Execution Context)

执行环境是一个对象「执行环境不是作用域」,存储各种信息「局部变量&作用域&参数&...」
执行环境有两部分:全局环境&局部环境
预编译阶段就是在进行执行环境的创建
首先创建环境->进栈->执行代码并赋值->执行完出栈

每当 JavaScript 执行一段代码时,都会创建对应的执行环境。
执行环境可以理解为一套记录函数运行状态的结构体,它包含:

  • 局部变量
  • 作用域链(外部环境的引用)
  • 参数信息
  • this 的指向

类型:

  1. 全局执行环境(程序启动时创建)
  2. 函数执行环境(函数调用时创建)

二、执行环境的内部结构

每一个执行环境都包含两个部分:

区域内容
全局环境内建对象、宿主对象、全局变量
局部环境局部变量、外部环境引用

示意图:

image.png

image.png

执行环境
 ├─ 全局环境
 │   ├─ 内建对象
 │   ├─ 宿主对象
 │   └─ 全局变量
 └─ 局部环境
     ├─ 局部变量
     └─ 外部环境

注意:全局执行环境中,局部环境与全局环境“合并”,因为它没有上级外部环境。


三、作用域与作用域链

作用域(Scope)
变量和函数的可访问范围。

  • 全局作用域:程序中任意位置都能访问的变量。
  • 局部作用域:函数内部定义的变量。
  • 块级作用域:由 {} 限定(let / const)。

作用域链(Scope Chain)
当访问变量时,JavaScript 会沿着作用域链向上查找:

  1. 当前函数作用域(局部变量)
  2. 外部函数作用域
  3. 全局作用域

四、作用域与调用栈的区别

来看两个例子👇

例 1:普通调用

let name = "Mike";
function fn() {
  let name = "Alan";
  fn2();
  function fn2() {
    console.log(name);
  }
}
fn(); // Alan

调用栈顺序:

全局 -> fn() -> fn2()

fn2() 可以访问 fn() 的变量,是因为 fn2 在创建时就“记录”了外部作用域(词法作用域),与调用栈位置无关。


例 2:闭包情况

let name = "Mike";
function fn() {
  let name = "Alan";
  return function fn2() {
    console.log(name);
  };
}
const fn2 = fn();
fn2(); // Alan

调用栈顺序:

全局 -> fn() [执行完出栈] -> fn2()

虽然 fn() 已经执行完毕并出栈,但 fn2() 依然能访问 fn() 中的变量。
这说明:

作用域在函数创建时就已确定,不会因调用顺序而改变。


五、闭包与外部环境

  • 每个执行环境在创建时,都会保存其外部环境引用(outer environment reference)
  • 这条引用链组成了作用域链。
  • 当函数被返回(形成闭包)时,其外部环境引用依旧保存在内存中。
function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  };
}

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

inner() 形成闭包,持有对 outer() 的外部环境引用,因此能持续访问 count


六、全局与局部执行环境创建过程

以以下代码为例:

let name = "Mike";
function fn() {
  let name = "Alan";
  return function fn2() {
    console.log(name);
  };
}
const fn2 = fn();
fn2(); // Alan

执行阶段分为两步:

  1. 创建全局执行环境

    • 建立全局对象、全局变量。
    • 记录函数声明(存入全局变量对象)。
  2. 函数执行时

    • 创建函数执行环境(包括局部变量与外部引用)。
    • 形成新的作用域链。

函数查找变量的顺序:

局部变量 → 外部环境变量 → 全局变量

七、总结

概念功能说明
调用栈管理函数执行顺序先进后出
执行环境存储函数运行时信息包含变量、作用域、this
作用域控制变量可见性函数创建时决定
作用域链变量查找路径向外层逐级查找
闭包保留外部环境引用实现数据持久化

调用栈&作用域.drawio.png