JavaScript单例模式本质是手动控制构造函数始终返回同一实例,核心在控制而非声明;需用闭包或静态私有字段缓存实例,并统一通过getInstance访问,避免new绕过控制。
JavaScript 中没有原生的“单例类”语法,所谓单例模式,本质是**手动控制一个构造函数只返回同一个实例对象**,关键在“控制”而非“声明”。
单例的核心不是避免 new,而是确保多次调用构造逻辑后,始终返回同一引用。常见错误是只做了一次初始化但没缓存结果,或用了闭包却没暴露统一入口。
this instanceof Singleton 无效——每次 new 都会创建新对象window.instance)污染作用域,且无法模块化利用函数作用域保存私有实例,通过静态方法提供受控访问。比模块导出更灵活,支持延迟初始化和参数传入。
function Logger() {
if (Logger.instance) {
return Logger.instance;
}
this.logs = [];
Logger.instance = this;
}
Logger.prototype.add = function(msg) {
this.logs.push(msg);
};
Logger.getInstance = function() {
return Logger.instance || new Logger();
};
调用时统一走 Logger.getInstance(),而不是 new Logger()。这样即使后续修改构造逻辑(比如加异步初始化),也只需改 getInstance 内部。
用 #instance 私有字段 + 静态 getter,语义更清晰,且避免意外覆盖静态属性:
class Database {
static #instance = null;
constructor(url) {
if (!Database.#instance) {
this.url = url;
this.connection = null;
Database.#instance = this;
}
return Database.#
instance;
}
static getInstance(url) {
if (!Database.#instance) {
return new Database(url);
}
return Database.#instance;
}
}
注意:new Database(url) 仍可能绕过控制——必须约定只用 getInstance。TypeScript 可加私有构造器进一步约束,但 JS 运行时无法强制。
单例不是万能解耦方案。以下情况反而会让代码更难测、更难维护:
destroy 方法,导致内存泄漏getInstance() 但传入不同参数(如不同 API 地址),后者会静默覆盖前者真需要“唯一性”的场景,优先考虑依赖注入或顶层 Provider(如 React Context、Vue provide/inject),而不是硬编码单例逻辑。