闭包实现单例模式的核心是复用同一对象实例(r === r2 为 true),而非共享变量;需用 let 声明私有 instance,延迟初始化、避免 var、状态属实例自身、方法用普通函数、适配 SSR/HMR/跨端,并严格验证 === 相等性。

闭包实现单例模式,核心不是“共享变量”,而是“复用同一个对象实例”。只要每次获取的都是内存中同一个引用(r === r2 为 true),才算真正满足单例定义。它天然避免全局污染、支持懒加载、无需类或 new 关键字,特别适合配置管理器、日志器、弹窗控制器、WebSocket 管理器等无继承需求的轻量场景。
必须把实例创建延迟到首次调用时
不能在模块顶层就执行 new 或构造逻辑,否则可能因环境未就绪(如 SSR 中没有 window/document)而报错,或造成不必要的资源开销。
- 用 let instance = null 声明私有变量,放在立即执行函数(IIFE)内部
- getInstance 方法内做判断:if (!instance) { instance = createInstance() }
- 返回 instance,而不是每次都调用 createInstance()
- 禁止使用 var —— 变量提升会破坏作用域封闭性
实例状态必须属于对象自身,而非闭包变量
常见误区是把 counter、socket、modals 等状态放在闭包里共用,看似“数据一致”,实则每次返回的是不同对象 —— 这只是共享状态的工厂函数,不是单例。
- 正确做法:状态作为实例的属性,例如
{ counter: 0, getCounter() { return this.counter } } - 方法必须用普通函数(非箭头函数),确保 this 指向当前实例
- 避免暴露闭包变量(如直接 export counter),防止外部意外修改
要适配真实运行环境的限制
前端项目常面临 SSR、HMR、跨端兼容等约束,闭包单例需主动应对,不能只写“能跑”的代码。
- 涉及 DOM 或 window 的操作(如创建弹窗、初始化 WebSocket),必须在 getInstance 内部加
typeof window !== 'undefined'判断 - 模块热更新(如 Webpack HMR)或重复 require 会导致闭包重执行,instance 被重置 —— 应确保该模块只被引入一次(如统一在入口文件 import)
- uni-app 等跨端框架中,必须用
uni.connectSocket替代原生 WebSocket,并在 onReady 后触发连接 - 若初始化依赖异步资源(如 i18n 配置),需缓存 Promise,避免每次调用都重新 fetch
验证是否真的唯一,别靠感觉
单例是否生效,最硬的指标只有一个:两次 getInstance() 返回的对象是否严格相等。
- 写两行代码验证:
const a = manager.getInstance(); const b = manager.getInstance(); console.log(a === b); // 必须为 true - 如果为 false,说明你返回了多个对象,哪怕它们读写同一个闭包变量,也不符合单例语义
- 可进一步测试状态同步:
a.setValue(100); console.log(b.getValue()); // 应输出 100

















