
本文解析为何仅靠闭包共享变量不构成单例模式,并演示如何通过延迟初始化与实例缓存构建符合定义的单例——确保全局唯一对象实例,而非仅共享状态。
本文解析为何仅靠闭包共享变量不构成单例模式,并演示如何通过延迟初始化与实例缓存构建符合定义的单例——确保全局唯一对象实例,而非仅共享状态。
单例模式(Singleton Pattern)的核心契约非常明确:整个应用生命周期中,该类/模块必须且只能存在一个实例对象。每次获取实例时,返回的必须是同一个引用(=== 相等),而非多个结构相同、行为一致但彼此独立的对象。你提供的代码虽实现了“状态共享”,却违背了这一根本原则。
关键问题在于 getInsctance() 方法每次都调用 createInstance() 创建全新对象:
const createInstance = () => ({
getCounter: () => counter,
addCounter: (num) => counter += num,
});
return {
getInsctance: () => createInstance() // ❌ 每次都 new 一个新对象!
};尽管 counter 变量被闭包捕获并被所有对象共享,但 r 和 r2 实际上是两个不同的对象实例:
console.log(r === r2); // false ← 这直接违反单例定义
真正的单例必须缓存首次创建的实例,并在后续调用中复用它。修正后的实现如下:
const singleton = (function() {
let instance;
const createInstance = () => ({
counter: 0, // 状态属于实例自身,而非闭包
getCounter() { // 使用普通函数,确保 this 指向实例
return this.counter;
},
addCounter(num) {
this.counter += num;
return this.counter;
}
});
return {
getInstance: () => instance || (instance = createInstance()) // ✅ 首次创建并缓存,后续直接返回
};
})();
// 验证唯一性
const r = singleton.getInstance();
const r2 = singleton.getInstance();
console.log(r === r2); // true ← 符合单例本质
r.addCounter(12);
r2.addCounter(32);
console.log(r.getCounter()); // 44
console.log(r2.getCounter()); // 44(同一对象,同一属性)⚠️ 注意事项:
- 避免箭头函数作为方法:箭头函数不绑定 this,无法访问实例属性(如 this.counter),必须改用传统函数声明;
- 闭包变量 ≠ 实例状态:你原代码中的 counter 是闭包私有变量,被所有“伪实例”共享,但这掩盖了多实例事实,易引发逻辑混淆;
- 赋值无效:singleton.counter = 100 对实例状态无影响,因为 singleton 是工厂对象,其自身不持有 counter 属性;状态应存在于单例实例内部;
- 设计合理性提醒:计数器本身通常不适合单例——多数场景需要多个独立计数器(如用户登录次数、API 请求统计等)。单例更适用于配置管理器、日志服务、数据库连接池等全局唯一资源。
总结:单例的本质是实例唯一性,而非状态一致性。实现时务必通过缓存机制(如 instance || (instance = create()))确保 getInstance() 始终返回同一对象引用,并将状态封装在该实例内部,才能真正符合设计模式规范。

















