从执行上下文和作用域链角度彻底理解 JavaScript 闭包
KongHou

从执行上下文和作用域链角度彻底理解 JavaScript 闭包

“闭包不是语法特性,而是执行机制的自然结果。”
— ECMAScript 规范解读


(ps: 由于笔者在学习相关内容时尚未明确 ES6 以后 VE 是否已经被移除,因此在本文中暂时只以 LE 来指代词法环境)

一、前言

在 JavaScript 学习中,“闭包(Closure)”是一个既熟悉又抽象的概念。
我们常听到它的定义:

闭包是函数与其词法作用域的组合。

但如果不了解“执行上下文(Execution Context)”和“词法环境(Lexical Environment)”,
这句话依然让人一头雾水。

今天,我们从 引擎的执行机制 出发,
执行上下文 + 作用域链(LE 链) 的方式,完整拆解闭包的本质。


二、执行上下文(Execution Context)

当 JavaScript 代码运行时,每一段可执行的代码(全局、函数、模块等)
都会对应一个“执行上下文”(简称 EC)。

每个执行上下文中包含三个核心部分:

名称 含义
Lexical Environment(LE) 记录当前作用域中定义的变量和函数
thisBinding 当前上下文的 this 值

三、词法环境(Lexical Environment)

词法环境 是一个内部结构,包含两部分:

1
2
3
4
LexicalEnvironment = {
EnvironmentRecord: { 变量名 → 值 },
Outer: 指向外层的 LE
}

👉 EnvironmentRecord 存放了本作用域内的所有变量、函数声明、参数等。
👉 Outer 指针则形成了一条 作用域链(Scope Chain)


四、用例子理解闭包形成过程

我们从一个最经典的例子出发 👇

1
2
3
4
5
6
7
8
9
10
11
12
function outer() {
var count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}

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

五、闭包形成的全过程(执行上下文视角)

🧩 Step 1:创建全局上下文

全局代码开始执行时,引擎创建 EC_Global

1
2
3
4
5
EC_Global:
LE_Global:
outer -> <function outer>
fn -> TDZ (稍后赋值)
Outer -> null

🧩 Step 2:执行 outer() 创建函数上下文

调用 outer() 时,引擎创建新的执行上下文 EC_outer

1
2
3
4
5
EC_outer:
LE_outer:
count -> undefined
inner -> <function inner>
Outer -> LE_Global

⚙️ 这里有一个关键动作:
当解析 function inner() {} 时,引擎会 创建函数对象 inner,并立即绑定环境引用

1
inner.[[Environment]] = LE_outer ✅

🧩 Step 3:返回 inner 并销毁 outer 的执行上下文

outer() 执行完毕后,它的执行上下文(EC_outer)出栈。
但由于 inner.[[Environment]] 仍然持有对 LE_outer 的引用,
所以 LE_outer 不会被垃圾回收

这就是闭包存在的根本原因。

1
2
fn = inner
inner.[[Environment]] -> LE_outer

🧩 Step 4:执行 fn()(即 inner)

调用 fn() 时:

1
2
3
EC_inner:
LE_inner:
Outer -> LE_outer

执行 console.log(count) 时:

  • 引擎先在当前 LE_inner 查找 count
  • 找不到,沿 Outer 找到 LE_outer
  • 修改了 LE_outer 中的 count

于是每次执行 fn() 时,count 都能保持上一次的状态。


六、用图表示闭包结构(ES6 版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──────────────────────────────┐
│ EC_Global │
│ LE_Global: │
│ outer → <function outer> │
│ fn → TDZ / <function> │
│ Outer → null │
└──────────┬───────────────────┘
│ 调用 outer()

┌──────────────────────────────┐
│ EC_outer │
│ LE_outer: │
│ count → 0 │
│ inner → <function inner> │
│ Outer → LE_Global │
└──────────┬───────────────────┘
│ inner.[[Environment]] = LE_outer

┌──────────────────────────────┐
│ EC_inner (fn 调用时创建) │
│ LE_inner: │
│ Outer → LE_outer │
└──────────────────────────────┘

七、闭包的本质

闭包是函数与其定义时的词法环境的组合。

它能“记住”外部函数作用域中的变量,
即使外部函数已经执行完毕,作用域链依然被保留。

换句话说:

函数创建时的 [[Environment]] 指针,
是闭包存在的根本机制。


八、总结

概念 含义 示例
执行上下文(EC) JS 代码执行的运行容器 Global、函数调用
词法环境(LE) 存储变量与外部引用 { EnvironmentRecord, Outer }
闭包(Closure) 函数 + 定义时的 LE inner.[[Environment]] = LE_outer

这里用一副图来大致概况本文内容:
闭包结构图

Powered by Hexo & Theme Keep
Total words 23.5k