ao与go分属不同执行上下文,ao是函数调用时临时创建、本该销毁的私有变量容器,闭包通过作用域链引用使ao被go间接挽留而存活。

活动对象(AO)和全局对象(GO)在闭包中不是并列存在的两个“对象池”,而是分属不同执行上下文、按需创建且生命周期分明的内存结构。理解它们的关键,不在于对比谁“更大”或“更优先”,而在于看清闭包如何让 AO 在本该销毁时被 GO(或其引用链)意外“挽留”。
AO 是函数私有上下文的临时变量容器
每次函数调用,JS 引擎都会为它新建一个 AO,用来存形参、var 声明的变量、arguments、内部函数声明等。这个 AO 只属于这一次调用,执行一结束,AO 就该被回收——除非有外部引用还抓着它。
- AO 不是全局共享的,同一函数多次调用,会产生多个互不干扰的 AO
- AO 中的变量初始值是 undefined,之后才按顺序赋值(这就是变量提升的底层表现)
- AO 本身没有名字,你无法直接访问它;它只作为函数执行期的内部机制存在
GO 是浏览器提供的顶层命名空间,天然持久
在浏览器里,GO 就是 window 对象,它从页面加载起就存在,直到标签页关闭。所有全局 var/function 声明,都会作为属性挂到 GO 上(比如 var a = 1 → window.a = 1)。但 let/const 不会挂 GO,这是 ES6 的明确设计。
- GO 不参与函数调用的进栈出栈,它始终驻留在堆内存中
- GO 本身不会“持有”某个函数的 AO,但它可能通过闭包间接引用 AO 中的变量
- console.log(window.x) 不报错但值为 undefined,是因为它查的是 GO 属性;而 console.log(x) 报 ReferenceError,是因为先查 VO/AO 再查 GO,两处都找不到
闭包让 AO “活下来”的真实路径
闭包不是魔法,它只是函数返回了一个内部函数,而这个内部函数的作用域链(Scope Chain)里,保留了对外层函数 AO 的引用。只要这个内部函数还活着(比如被赋值给全局变量、传给定时器、作为回调保存),它的作用域链就不会释放,AO 就不能被垃圾回收。
- AO 本身不会“移到”GO 里,它仍待在堆内存某处,只是被作用域链持续引用
- GO 可能成为闭包的“落脚点”:比如 var fn = outer(),fn 被挂到了 window 上,而 fn 的[[Environment]] 指向 outer 的 AO —— 这时 GO 通过 fn 间接维系了 AO 的存活
- 真正被“闭包住”的,不是整个 AO,而是 AO 中被内部函数实际读取的那些变量(引擎可能做优化,只保留用到的部分)
一个典型例子帮你串起来
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const inc = createCounter(); // 此时 createCounter 的 AO 并未销毁
console.log(inc()); // 1 → 访问并修改 AO 中的 count
这里:createCounter 执行时生成 AO(含 count);函数返回后,AO 没被销毁,因为返回的匿名函数的作用域链引用了它;inc 被赋值给全局变量,等于把闭包“登记”在 GO 下;后续每次调用 inc,都是在操作那个被“冻结”在堆里的 AO 实例中的 count。











