单例工厂是单例与工厂模式的组合实践,核心在于通过闭包+静态缓存实现插件实例全局唯一、延迟创建及动态参数注入,避免构建时硬编码导致的配置错乱或重复初始化。

单例工厂不是标准设计模式,而是单例 + 工厂的组合实践;它在前端插件体系中真正起作用的地方,是让插件实例全局唯一,且创建时机可延迟、参数可动态注入——而不是一上来就 new 所有插件。
你遇到的问题大概率是:插件初始化逻辑耦合了环境配置(如 API 基地址、用户权限、主题色),但这些值在构建时不可知,必须等 runtime 从后端或 localStorage 拿到才生效。硬编码 new 插件会导致配置错乱或重复初始化。
下面直接说怎么做、为什么、容易踩什么坑。
单例工厂的核心实现:用闭包 + 静态缓存控制实例生命周期
关键不是“写个 Singleton 类”,而是把工厂函数和实例缓存绑死在一个作用域里,避免多处调用产生多个实例。
常见错误是把 getInstance 写成普通导出函数,却忘了缓存变量被多次 import 触发重初始化(尤其在 webpack 的 HMR 或微前端场景下)。
- 必须用模块级私有变量(如
let instance = null)或WeakMap关联 factory 函数与实例 - 工厂函数本身要接收 config 参数,但只在第一次调用时生效,后续调用忽略传参(否则会覆盖已有状态)
- 不要在构造函数里读取 config —— 构造时机不可控;应在
init(config)方法中做真正初始化
const pluginFactory = (() => {
let instance = null;
return (config) => {
if (!instance) {
instance = new PluginCore();
instance.init(config); // 真正加载配置、注册事件、发起预请求
}
return instance;
};
})();
<p>// 使用
const plugin = pluginFactory({ apiBase: '/v2', theme: 'dark' });
</p>
如何支持运行时动态切换环境(比如切测试/生产 API)
直接修改单例内部 config 是危险的 —— 多个插件模块可能已基于旧 config 渲染 UI 或缓存数据。正确做法是「惰性重建 + 平滑过渡」。
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
- 提供
rebuild(config)方法,内部先调用dispose()清理副作用(取消 pending 请求、移除 event listener、清空内存缓存) - 再 new 一个新实例,但保留旧实例直到新实例 ready(例如通过
new Promise(resolve => { /* 等待 init 完成 */ })) - 暴露
isReady属性或onReady(cb),让上层组件决定何时切换
注意:rebuild 不应是同步替换,否则 UI 可能闪退或报错 “Cannot read property of undefined”。
微前端或插件热更新场景下的实例隔离问题
如果插件被多个子应用(如 qiankun 的 micro-app)共用,pluginFactory 的闭包变量会被所有子应用共享 —— 这就是坑的根源。
- 不能依赖模块顶层变量缓存;改用
window.__PLUGIN_INSTANCES__或globalThis下带命名空间的键(如__PLUGIN_INSTANCES__['analytics-v2']) - 每个插件工厂函数需声明自己的 namespace,例如
createAnalyticsPlugin('analytics-v2') - 在插件卸载(unmount)时,主动 delete 对应 key,避免内存泄漏
否则你会看到:子应用 A 切换路由后插件没销毁,子应用 B 初始化时拿到的是 A 的残留实例,请求 header 带着 A 的 auth token。
为什么不用 class + static getInstance?
因为 class 声明无法天然隔离不同配置的实例。你不能靠 Plugin.getInstance({ env: 'test' }) 和 Plugin.getInstance({ env: 'prod' }) 得到两个独立实例 —— static 变量只有一个。
- 若真需要多环境并存,工厂函数必须是可复用的模板,例如
createPluginFactory(PluginCore)返回一个新工厂 - 或者用
WeakMap把 config 对象作为 key 缓存实例(但要注意 config 对象引用变化会导致重复创建)
最简方案仍是每个插件对应一个独立工厂模块,由上层容器按需导入 —— 而不是试图让一个类扛住所有环境变体。
实际最难的从来不是写对 getInstance,而是搞清「这个插件到底该在哪个时刻、被谁、以什么方式销毁」。环境切换不是改个 URL 就完事,它牵扯请求队列、本地缓存、UI 状态同步。单例工厂只是把入口收束了,真正的复杂度藏在 init 和 dispose 的契约里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










