JavaScript单例模式通过static方法配合静态私有属性实现线程安全、全局唯一实例:类自身持有_instance引用,首次调用getInstance()创建并缓存,后续直接返回;构造函数通过运行时校验防止外部new,兼容旧环境可用static getter模拟。

JavaScript 中可以通过 static 方法配合闭包或静态私有属性,实现线程安全(单次初始化)、全局唯一实例的单例模式。核心思路是:类自身持有实例引用,首次调用 static 方法时创建并缓存,后续直接返回该实例。
使用 static 属性 + static 工厂方法(推荐)
这是最清晰、易读且符合 ES6+ 习惯的方式。利用 class 的 static 字段(需环境支持,或用 static getter 模拟)保存实例:
- 定义一个私有静态属性(如
_instance)存储唯一实例 - 提供 static
getInstance()方法:检查是否存在实例,不存在则 new 一次并赋值,然后返回 - 构造函数可设为私有语义(例如通过标记位或 throw 错误防止外部 new)
示例:
class Logger {
static _instance = null;
constructor() {
if (Logger._instance) {
throw new Error('Use Logger.getInstance() instead.');
}
this.logs = [];
}
static getInstance() {
if (!Logger._instance) {
Logger._instance = new Logger();
}
return Logger._instance;
}
log(message) {
this.logs.push(`[${new Date().toISOString()}] ${message}`);
}
}
// 使用
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
console.log(logger1 === logger2); // true
兼容旧环境:用 static getter 替代 static 字段
若目标环境不支持顶层 static 字段(如 Node.js 14 以下或某些打包配置),可用 static getter 模拟延迟初始化:
立即学习“Java免费学习笔记(深入)”;
- 在 getter 内部用闭包或模块级变量缓存实例
- 避免在类定义时就执行构造逻辑,更可控
示例(模块作用域缓存):
// logger.js
let instance = null;
export class Logger {
static get instance() {
if (!instance) {
instance = new Logger();
}
return instance;
}
constructor() {
if (instance) {
throw new Error('Singleton: use Logger.instance');
}
this.logs = [];
}
log(msg) {
this.logs.push(msg);
}
}
注意点与常见误区
- 不能依赖 static 方法自动“单例化”:static 方法本身不阻止多次调用 new,必须显式控制实例创建逻辑
- 构造函数不可完全私有:JS 无真正 private constructor,只能靠约定 + 运行时校验(如 throw 错误)来约束
- 避免在 static 方法中传参初始化:带参数的单例会破坏“唯一性”语义;如需配置,应在首次 getInstance 时传入,并缓存配置结果
-
多模块/多上下文场景要小心:若代码被多次 import(如未正确设置 package 的 exports 或存在重复打包),可能产生多个独立的
_instance变量,导致多个“单例”
进阶:带初始化参数的单例(谨慎使用)
如果确实需要首次获取时传入配置(如 API endpoint),可扩展 getInstance(options),但只允许首次调用传参,之后忽略或报错:
class ApiClient {
static _instance = null;
static _options = null;
static getInstance(options = {}) {
if (ApiClient._instance) {
if (Object.keys(options).length > 0) {
console.warn('ApiClient already initialized; ignoring new options');
}
return ApiClient._instance;
}
ApiClient._options = { baseURL: 'https://api.example.com', ...options };
ApiClient._instance = new ApiClient(ApiClient._options);
return ApiClient._instance;
}
constructor(options) {
this.baseURL = options.baseURL;
}
}


















