普通script变量挂载全局或函数作用域,回收依赖执行流退出与引用状态;控制台变量隐式绑定调试上下文且常被强引用,基本不自动回收;模块变量受词法作用域和导入链路保护,回收等同页面卸载。

普通 <script></script> 标签中的变量、控制台临时输入的变量,以及模块脚本(type="module")中的变量,虽然都由 JavaScript 引擎管理内存,但它们的生命周期和垃圾回收时机存在关键差异——核心区别在于作用域绑定方式和执行环境的持久性。
普通 script 标签变量:函数级+全局级,回收依赖执行流退出与引用状态
在非模块的 <script></script> 中声明的变量,按作用域分为两类:
-
全局变量(如
var a = {}或直接赋值b = []):挂载在全局对象(浏览器中是window)上,只要页面未卸载,就不会被回收;即使后续代码不再访问,引擎也无法判定其“不可达”,因此长期驻留内存。 -
函数内局部变量(如
function foo() { let obj = {}; }):函数执行结束时,若obj未被闭包捕获、未被全局/外部变量引用,则在下一次标记清除周期中被标记为待回收对象。回收不是立即发生,而是由引擎按需触发(通常在内存压力上升或空闲时)。
控制台临时变量:隐式挂载全局,实际永不自动回收
在浏览器开发者工具控制台中直接输入并执行的语句(如 let x = {data: new Array(1000000)}),其行为取决于当前控制台的执行上下文:
- 在默认全局上下文中,
let/const声明仍会绑定到一个特殊的控制台作用域,但该作用域不会随单次输入结束而销毁;V8 等引擎会保留这些变量供后续调试使用。 - 更关键的是:控制台变量常被内部调试器对象(如
$0,$_)或历史记录引用,形成隐式强引用,导致它们在用户手动清空控制台或关闭标签页前,基本不会被标记为可回收。 - 实践中,大量在控制台创建的大对象(如 DOM 节点、大型数组)极易造成假性内存泄漏,需主动设为
null或使用%clear命令释放。
模块脚本变量:词法作用域封闭,静态导入决定可达性边界
ES 模块(<script type="module"></script>)拥有独立的模块作用域,且模块是单例、静态解析、延迟求值的:
- 模块顶层声明的
let/const/class不挂载到window,只存在于模块自身的词法环境中;模块未被任何其他模块import时,可能根本不会执行,自然无变量生成。 - 一旦模块被加载并执行,其导出绑定(
export)会被其他模块持有引用;只要存在至少一个活跃的import链路,整个模块作用域内的变量(包括未导出的)都视为“可达”,不会被回收。 - 模块本身无法被动态卸载(目前标准不支持
import.meta.deactivate等机制),所以模块变量的回收时机等同于页面卸载——除非使用import()动态导入后,显式切断所有引用(如将导入结果设为null,且无其他闭包持有),才可能在后续 GC 中被清理。
本质上,三者回收时机的差异不在 GC 算法本身(现代引擎统一用标记清除),而在于变量是否还处于某个“根可达”路径上:全局对象、调试器上下文、模块导出表,都是 GC 的根集合组成部分。只要变量能从任一根本源追溯到,就不会被回收。











