标准答案
- 模块首次加载执行顶层代码并缓存 exports,后续加载通常返回同一对象。
- 单例适合连接池、配置和无状态工具,但不能把用户、租户或请求状态放在模块变量中。
- 测试需要隔离模块缓存或改用工厂注入依赖,避免测试顺序影响结果。
- 热加载、多个路径和 ESM/CJS 混用可能产生多个看似相同的实例。
题目解析
CommonJS 首次 require 会执行顶层代码并按解析后的路径缓存 exports,后续调用通常复用同一实例。这适合无状态工具、连接池和配置,但模块级可变状态会跨请求共享。
用户、租户、请求和临时任务状态不能放在模块变量中,否则会出现串请求、测试顺序污染和内存长期持有。测试需要工厂注入、隔离模块缓存或显式 reset。
路径差异、Symlink、ESM/CJS 混用和热加载可能产生多个看似相同的实例。排查单例问题要看实际解析路径、加载器和进程生命周期。
常见误区
- 误区:把 module-level 变量当作请求上下文。改正:使用请求上下文、AsyncLocalStorage 或显式参数传递。
- 误区:测试修改导出对象后不恢复。改正:隔离模块缓存、在 afterEach 清理,避免测试顺序影响结果。
- 误区:以为每次 require 都重新初始化连接。改正:理解模块缓存,连接生命周期由模块/应用关闭流程统一管理。