模块卸载依赖js引擎常规gc,需清除所有强引用并避免长期副作用。动态import模块无自动回收机制,须确保命名空间对象不可达、无未清理监听器或定时器、导出内容不被持有,且打包工具未生成隐式引用。

模块系统本身没有独立的“垃圾回收”机制。所谓模块卸载,实际依赖底层 JS 引擎对对象的常规垃圾回收(GC)——前提是该模块导出的对象、闭包、监听器等所有强引用都被清除,且模块自身不再被任何活跃代码持有所需的引用。
动态加载模块(如 import() 表达式)返回一个 Promise,解析后得到一个模块命名空间对象。这个对象一旦被变量持有、被函数闭包捕获、或被注册为事件处理器/定时器回调,就会延长其生命周期。只有当它彻底“不可达”时,引擎才可能在后续 GC 周期中回收其所占内存。
动态 import 模块的卸载条件很严格
- 没有变量直接或间接引用该模块命名空间对象(包括全局变量、闭包自由变量、数组/Map/Set 中的值);
- 模块内部没有长期存活的副作用:比如未清理的
addEventListener、未取消的setTimeout/setInterval、未解绑的AbortSignal监听、未释放的fetch流或WebWorker引用; - 模块导出的类实例、函数、配置对象等,没有被其他模块或业务逻辑持续持有;
- 打包工具(如 Webpack、Vite)未因代码分割策略生成隐式持久引用(例如
import.meta.webpackHot或预加载逻辑)。
常见误判场景
- 你以为
import('./foo.js').then(m => { /* 使用完 */ })后模块就“没了”,但若m被意外赋给了window.currentModule或存入某个缓存 Map,它就一直活着; - 模块内定义了默认导出函数,而该函数被传入
document.addEventListener('click', fn),只要监听未移除,整个模块作用域链就维持着; - 使用
React.lazy+Suspense时,组件被卸载后,模块对象是否释放取决于 React 内部是否保留对它的弱引用——目前主流版本并不主动释放,而是交由引擎自然回收。
不能靠 import() 自动管理内存
JS 引擎不会因为某个模块是“动态加载”的,就赋予它特殊回收优先级。它和普通对象一样,只看可达性。你无法强制卸载模块,也不能调用类似 unloadModule() 的 API(浏览器环境不存在)。
可辅助判断的方式
- 在 Chrome DevTools 的 Memory 面板中录制 Heap Snapshot,筛选
Module类型对象,对比操作前后数量变化; - 使用
performance.memory(如果可用)或window.gc()(仅部分 Chromium 实验标志启用)触发手动 GC 后观察内存回落; - 对关键路径显式清空引用:
mod = null; delete cache[key];等,减少强引用残留。
本质上,这不是模块系统的功能,而是你能否写出“无泄漏引用”的模块使用逻辑。










