作用域链不会被主动释放,而是随执行上下文销毁自然失效;它本质是环境记录的只读引用链,真正释放的是执行上下文及其变量环境、词法环境等栈空间和关联堆引用。

作用域链本身不会“被释放”,它只是随着执行上下文的销毁而自然失效。关键在于:作用域链不是独立内存结构,而是执行上下文的一个只读引用链;真正释放的是执行上下文对象及其内部变量环境、词法环境等所占的栈空间和关联的堆引用。
下面分几个层面说清楚这个过程:
作用域链的生命周期绑定执行上下文
每个函数在定义时,其内部属性 [[Scope]] 就已静态保存了当时所在词法作用域的链表(即外层函数的词法环境 + 全局词法环境)。
但这个链表只是“指向”,不复制数据。
当函数被调用,创建新的执行上下文时,会基于 [[Scope]] 构建运行时的作用域链 —— 实际是把当前函数的词法环境推到链首,再链接外部环境(outer 链)。
✅ 所以作用域链本质是一组环境记录(Environment Record)的引用序列,不是单独分配的内存块。
函数调用结束 → 执行上下文出栈 → 作用域链自动失效
- 调用栈(Call Stack)是栈结构,后进先出。
- 函数执行完毕,其执行上下文从栈中弹出。
- 弹出的同时,该上下文持有的:
- 变量环境(var 声明)
- 词法环境(let/const 声明)
- this 绑定
- outer 链指针
全部随栈帧一起被系统回收(栈内存即时释放)。
例如:
function outer() {
let x = "outer";
function inner() {
console.log(x); // 通过作用域链访问 outer 的词法环境
}
return inner;
}
const fn = outer(); // outer 执行完,其执行上下文出栈
此时 outer 的执行上下文已销毁,但它创建的 inner 函数的 [[Scope]] 仍保留对 outer 词法环境的引用 —— 这就形成了闭包。
⚠️ 注意:这不是“作用域链没释放”,而是 inner 的闭包持有了对外部环境的强引用,阻止了垃圾回收器回收那个词法环境对应的堆内存(如其中的 x 变量对象)。
真正决定内存是否释放的关键是引用计数与可达性
- 栈内存(执行上下文):函数返回即释放,无需 GC。
- 堆内存(如闭包捕获的变量对象、对象字面量):只有当没有任何活跃引用指向它时,才会在下一次垃圾回收周期被清理。
- 如果某个函数返回了内部函数,且该内部函数仍被持有(如赋值给全局变量、传入定时器等),那么它
[[Scope]]中引用的外层词法环境就依然“可达”,对应堆内存不会释放。
简单总结释放逻辑:
- 栈上作用域链相关结构:随执行上下文出栈,立即消失
- 堆上被闭包引用的数据:只要还有活跃引用,就继续存活;引用断开后,下次 GC 清理
- 没有闭包的普通函数:执行上下文一出栈,所有局部变量(包括词法环境中的 let/const)彻底消失,堆中若无其他引用,对应对象也很快被回收
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











