闭包是被延长寿命的ao引用链,源于词法作用域固化(函数创建时锁定[scope])、执行上下文不销毁(ao因被引用而驻留堆内存)、垃圾回收被拦截(引用计数阻止ao回收)。

讲闭包底层原理,关键不是背定义,而是让人“看见”它怎么在内存里活下来的。核心就三点:词法作用域固化、执行上下文不销毁、垃圾回收被拦截。
词法作用域在函数创建时就锁死了
JS 函数不是运行时才决定能访问谁,而是在写代码那一刻(解析阶段)就记下了自己“出生地”的所有变量——这个记录叫 [Scope],是函数对象的内部属性。比如:
function outer() { const x = 10; return function inner() { return x; } }-
inner一被声明,它的 [Scope] 就已包含对outer活动对象(AO)的引用,和全局对象(GO) - 这跟
inner后来在哪调用无关——哪怕它被传到另一个文件里执行,[Scope] 里的路径没变
外部函数执行完,它的执行上下文没被清掉
通常函数执行完,它的执行上下文(EC)会出栈,AO 被 GC 回收。但一旦内部函数被外部持有(比如 return 出去、赋给变量、当回调),V8 就发现:AO 还被 inner 的 [Scope] 引用着 → 不能回收。
- 所以
outer执行结束,它的 AO(含x)仍驻留在堆内存中 -
inner每次执行,都是顺着自己的 [Scope] 去堆里找那个“没被收走的 AO”,读取或修改x - 这不是 JS “特意设计”的功能,而是词法作用域 + 引用计数式 GC 自然推出来的结果
闭包本质是“被延长寿命的 AO 引用链”
你可以把闭包理解为一个活的连接通道:内部函数 → 自己的 [Scope] → 外部函数的 AO → 其中变量。
- 只要通道一端(内部函数)还存在,另一端(AO)就不会被 GC 清理
- 多个闭包可以共享同一个 AO(比如
createCounter()返回多个方法,都闭包在同一个count上) - 一旦所有闭包都被释放(比如
func = null),AO 失去引用,下一次 GC 就会回收它
真正卡住人的,往往不是“它是什么”,而是“它为什么没被删”。盯住 [Scope] 和 GC 的互动,比死记“函数套函数”更接近底层真相。











