从执行上下文和作用域链角度彻底理解 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  | LexicalEnvironment = {  | 
👉 EnvironmentRecord 存放了本作用域内的所有变量、函数声明、参数等。
👉 Outer 指针则形成了一条 作用域链(Scope Chain)。
四、用例子理解闭包形成过程
我们从一个最经典的例子出发 👇
1  | function outer() {  | 
五、闭包形成的全过程(执行上下文视角)
🧩 Step 1:创建全局上下文
全局代码开始执行时,引擎创建 EC_Global:
1  | EC_Global:  | 
🧩 Step 2:执行 outer() 创建函数上下文
调用 outer() 时,引擎创建新的执行上下文 EC_outer:
1  | EC_outer:  | 
⚙️ 这里有一个关键动作:
当解析 function inner() {} 时,引擎会 创建函数对象 inner,并立即绑定环境引用:
1  | inner.[[Environment]] = LE_outer ✅  | 
🧩 Step 3:返回 inner 并销毁 outer 的执行上下文
当 outer() 执行完毕后,它的执行上下文(EC_outer)出栈。
但由于 inner.[[Environment]] 仍然持有对 LE_outer 的引用,
所以 LE_outer 不会被垃圾回收。
这就是闭包存在的根本原因。
1  | fn = inner  | 
🧩 Step 4:执行 fn()(即 inner)
调用 fn() 时:
1  | EC_inner:  | 
执行 console.log(count) 时:
- 引擎先在当前 
LE_inner查找count - 找不到,沿 
Outer找到LE_outer - 修改了 
LE_outer中的count值 
于是每次执行 fn() 时,count 都能保持上一次的状态。
六、用图表示闭包结构(ES6 版)
1  | ┌──────────────────────────────┐  | 
七、闭包的本质
闭包是函数与其定义时的词法环境的组合。
它能“记住”外部函数作用域中的变量,
即使外部函数已经执行完毕,作用域链依然被保留。
换句话说:
函数创建时的
[[Environment]]指针,
是闭包存在的根本机制。
八、总结
| 概念 | 含义 | 示例 | 
|---|---|---|
| 执行上下文(EC) | JS 代码执行的运行容器 | Global、函数调用 | 
| 词法环境(LE) | 存储变量与外部引用 | { EnvironmentRecord, Outer } | 
| 闭包(Closure) | 函数 + 定义时的 LE | inner.[[Environment]] = LE_outer | 
这里用一副图来大致概况本文内容: