控制台变量挂载window、repl变量写入globalthis、页面script执行后局部变量可回收——三者内存归属不同:控制台由页面生命周期兜底,repl依赖进程退出,script由引擎按引用关系回收。

控制台运行、Node REPL 和页面 <script></script> 标签看似都在“执行 JavaScript”,但内存分配方式和生命周期管理机制截然不同——关键不在“能不能跑”,而在于“谁管内存、何时释放、作用域怎么活”。
浏览器控制台:无模块包装,变量挂载在全局,不自动清理
你在开发者工具控制台里输入 let x = {a: 1};,这个对象直接分配在堆上,变量 x 是全局对象(window)的一个属性。它不会因为某次输入结束就销毁:
- 所有
let/const/var声明都绑定到window(非严格模式),相当于window.x = {...} - 没有模块封装,也没有
__dirname或module等上下文,所以不存在“作用域退出即回收”的机制 - 内存是否释放,取决于
window.x是否被显式删除或覆盖,或页面刷新
Node REPL:顶层代码在临时作用域求值,变量驻留但上下文断裂
REPL 不是真正的模块环境,也不是全局作用域的简单延伸。每次回车后,代码被包裹在一个匿名函数中执行(类似 (function(){...}).call(globalThis)):
- 定义的变量(如
const arr = [1,2,3])确实保留在globalThis上,后续仍可访问 - 但异常发生时,当前求值上下文终止,REPL 提示符可能卡住——不是内存泄漏,而是交互状态中断
- 没有
__filename/__dirname,因为没文件载体;也没有模块加载器介入,所以无法触发模块级 GC 友好清理
页面 script 标签:按块隔离,执行完即释放局部资源,但全局副作用持久
每个 <script></script> 是独立执行单元,其顶层作用域行为接近全局,但生命周期更可控:
- 同步脚本中声明的
let/const变量,在脚本执行完毕后若未被闭包捕获,对应的绑定记录可被引擎标记为待回收 - 函数内部变量随执行上下文弹出而自然释放;对象若不再被引用(包括未挂在
window上),会进入垃圾回收队列 - 模块脚本(
type="module")更严格:顶层this === undefined,变量不挂全局,且模块实例有明确的加载-求值-导出生命周期
根本差异:谁拥有内存,谁决定释放时机
三者共用 V8 引擎,但内存归属逻辑不同:
- 控制台:你手动输入 → 绑定到
window→ 由页面生命周期兜底(关标签才清) - REPL:你敲回车 → 临时函数执行 → 变量写入
globalThis→ 进程退出才真正释放 - 页面 script:HTML 解析器注入 → 执行上下文创建 → 执行完上下文销毁 → 引擎根据引用关系决定对象存活











