闭包是函数与其定义时词法环境的静态绑定,由[[environment]]属性固化、作用域链动态查找及gc保留引用共同实现;变量因此脱离原函数生命周期而在堆中持久驻留。

闭包不是语法结构,而是 JavaScript 引擎在函数创建和执行过程中自然形成的环境绑定。它的底层构建,关键在于三个机制的协同:词法环境(Lexical Environment)的静态捕获、作用域链(Scope Chain)的动态链接,以及垃圾回收(GC)对活跃引用的保留。
词法环境在函数定义时就被固化
当 JS 引擎解析函数声明(如 function inner() { console.log(x); })时,并不等它执行,就已确定它“出生地”的词法环境——即外层函数(或全局)中所有可访问的变量、参数、函数声明。这个环境被静态记录为该函数内部的一个隐藏属性(V8 中叫 [[Environment]]),与函数体一同存储。哪怕 inner 后来被赋值给全局变量、传入 setTimeout 或作为事件回调,它携带的 [[Environment]] 始终指向定义时的那个外层环境。
作用域链在每次调用时动态组装
函数真正执行时,引擎会为其创建执行上下文(Execution Context)。其中的作用域链并非预先拼好的完整链表,而是按需构建:从当前函数自身的词法环境开始,逐级向上链接到其 [[Environment]] 所指向的外层词法环境,再链接到外层的 [[Environment]],直至全局环境。这个链式结构决定了变量查找路径——比如 inner 执行时找不到 x,就顺着作用域链往上找,直到在 outer 的词法环境中命中。
闭包变量因引用而逃逸 GC
正常情况下,outer 函数执行完毕后,其执行上下文(含变量对象)会被标记为可回收。但若 inner 函数已被返回并赋值给外部变量(如 const fn = outer();),那么 inner 的 [[Environment]] 就持续持有对 outer 词法环境的强引用。V8 的垃圾回收器会检测到这种活跃引用,从而阻止 outer 词法环境及其内部变量(如 message、count)被回收。这些变量就“活”在堆内存中,专供闭包使用,直到 inner 本身也不再被任何地方引用。
实际表现:一个变量,两套生命周期
闭包让一个变量同时拥有两种生命周期:
- 语法层面:它属于 outer 函数的局部变量,作用域仅限于 outer 内部;
- 内存层面:只要闭包存在,它就在堆上长期存活,不受 outer 执行结束影响。
这种分离正是闭包强大又易出错的原因——它打破了“函数退出 → 变量销毁”的直觉,把局部变量升级为受闭包生命周期管理的“持久状态”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











