全局变量和模块导出均导致对象长期驻留内存:前者绑定于window生命周期,后者依赖静态模块引用链;二者均需主动切断引用才能回收,常见风险包括dom泄漏、循环依赖及打包器作用域干扰,应通过按需初始化、及时清理和弱引用等手段可控释放。

全局变量和模块导出都会让对象长期驻留在内存中,但机制不同:全局变量靠作用域生命周期锁定,模块导出靠静态绑定与引用链维持。它们都不自动销毁,必须主动切断引用才能触发回收。
全局变量几乎永不销毁
浏览器中全局变量挂载在 window(或 globalThis)上,其执行环境是整个页面生命周期。只要页面没关闭,这些变量就一直可达——垃圾回收器从根出发总能访问到它们。
- 未声明直接赋值(如
name = "test")会意外创建全局变量,尤其在非严格模式下 - 即使函数内定义了全局变量,它也不会随函数退出而释放
- 销毁方式只有显式赋值为
null或undefined,或删除属性(delete window.xxx),但后者不推荐
模块导出延长顶层变量存活期
ES 模块的 import/export 是静态单例绑定。一个模块被导入后,它的顶层作用域(包括函数、类、对象、缓存等)会持续保留在内存中,直到所有引用它的模块都被卸载——而目前主流引擎尚不支持运行时模块卸载。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 导出一个大对象(如
export const cache = new Map()),该对象会一直存在,哪怕只被用过一次 - 导出闭包(如
export const createHandler = () => { const data = new ArrayBuffer(10MB); return () => data.byteLength; })会让data随闭包长期驻留 - 模块间循环依赖会隐式维持环境记录间的强引用,进一步阻碍回收
两者共有的风险点
它们都容易引发“本该短期存在却长期持有”的问题,本质是维持了不该存在的强引用路径。
- 把 DOM 元素、定时器、事件监听器挂到全局或导出对象上,会导致整棵 DOM 树或数据结构无法释放
- 打包工具(如 Webpack)将模块包裹进 IIFE,可能使作用域更难被引擎识别为可回收,尤其含
debugger、with或 eval 时 - 导出对象被赋值给全局变量(如
window.api = await import("./api.js")),等于双重延长生命周期
可控释放的关键做法
不依赖“自动销毁”,而是设计可中断的引用链。
- 避免在模块顶层初始化重型资源;改用按需函数(如
initCache()),并在不再需要时调用清理逻辑 - 事件监听器绑定后务必配套
removeEventListener;定时器用完立即clearTimeout/clearInterval - 缓存类场景优先用
WeakMap或WeakRef存储,避免强引用拖住目标对象 - 动态加载模块时,业务结束前将导出值置为
null,并确保无其他变量持有该引用 - 用 Chrome DevTools 的 Memory 面板拍堆快照,筛选
Closure或Object类型,查看 Retainers 路径确认谁在持有所需释放的对象
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










