JS class 本身不保障单例,需通过静态方法(如 getInstance)或模块闭包控制实例创建逻辑;推荐 static instance + getInstance 模式,确保全局唯一引用且语义清晰。

JS 的 class 语法本身不提供单例保障,关键不是写 class,而是控制实例创建逻辑,确保只返回同一个对象引用。直接用 new MyClass() 每次都会生成新实例,必须绕过默认构造流程,用静态方法或闭包主动管理缓存。
用 static 属性 + getInstance()(推荐,语义清晰)
在类内部用 static 声明私有缓存变量,配合静态工厂方法统一出口:
- 首次调用
getInstance()时创建实例并存入MyClass.instance - 后续调用直接返回已存实例,不 new
- 构造函数不做限制(JS 无法真正私有化),靠文档+约定说明“请勿 new”
class Logger {
static instance = null;
constructor() {
this.logs = [];
}
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
log(msg) {
this.logs.push(msg);
}
}
// 使用
const a = Logger.getInstance();
const b = Logger.getInstance();
console.log(a === b); // true
用模块级闭包封装(更安全,避免污染)
把 class 和实例缓存一起放在模块顶层作用域中,外部只能通过导出的接口访问:
- 实例变量定义在模块内,不暴露给全局
- 导出的
getInstance或直接导出实例对象 - 天然隔离,多个模块导入得到的是同一份引用
// logger.js
class Logger {
constructor() {
this.logs = [];
}
log(msg) {
this.logs.push(msg);
}
}
let instance;
export const getInstance = () => {
if (!instance) instance = new Logger();
return instance;
};
// 或直接导出实例(更简洁)
// export default new Logger();
避免常见陷阱
-
不要依赖
new.target或抛错阻止 new:不可靠,调试和工具链可能绕过 -
别把实例挂到全局对象(如
window.Logger):易被覆盖、污染命名空间 -
带参数的懒加载需谨慎:若需初始化参数,建议首次
getInstance(options)传入,之后忽略后续参数,或抛错提示已初始化 -
注意内存泄漏:长期持有的单例若引用了 DOM 或大型数据,应提供
destroy()方法清理
为什么不用纯对象字面量?
对象字面量(const singleton = { ... })确实最简单,但它无法继承、不能用 instanceof 判断、方法里 this 易丢失。如果需要类的结构能力(比如扩展、类型提示、TS 支持),class + getInstance 是更可持续的选择。











