单例模式保证一个类全局只有一个实例,常用于全局配置、缓存、日志、数据库连接池等只需一份的场景。前端典型应用是全局状态管理(如 Vuex/Pinia 的 store)和 SDK 实例。本 demo 用 TypeScript 实现两种单例:经典的懒加载私有构造器写法,以及更通用的泛型单例基类,并讨论 JS 单线程下"线程安全"问题的真实含义。
// 单例模式:保证全局唯一实例
// 含懒加载与泛型单例基类
// ============ 写法一:经典懒加载单例 ============
class ConfigManager {
private static instance: ConfigManager | null = null;
private config: Record<string, unknown> = {};
// 私有构造器:阻止外部 new 实例化
private constructor() {}
// 懒加载:首次调用时才创建实例
static getInstance(): ConfigManager {
if (ConfigManager.instance === null) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
set<V>(key: string, value: V): void { this.config[key] = value; }
get<V = unknown>(key: string): V { return this.config[key] as V; }
}
// 应用:两处获取的是同一实例
const cfg1 = ConfigManager.getInstance();
const cfg2 = ConfigManager.getInstance();
console.log(cfg1 === cfg2); // true
// new ConfigManager(); // 报错:constructor 是 private
// ============ 写法二:泛型单例基类(复用单例逻辑) ============
abstract class Singleton<T> {
private static instances = new Map<Function, unknown>();
static getInstance<S extends Singleton<unknown>>(this: new () => S): S {
const ctor = this as unknown as Function;
if (!Singleton.instances.has(ctor)) {
Singleton.instances.set(ctor, new ctor());
}
return Singleton.instances.get(ctor) as S;
}
}
// 业务类继承单例基类,自动获得 getInstance
class Logger extends Singleton<Logger> {
private count = 0;
log(msg: string): void { this.count++; console.log('[' + this.count + '] ' + msg); }
}
const log1 = Logger.getInstance();
const log2 = Logger.getInstance();
log1.log('hello'); // [1] hello
log2.log('world'); // [2] world —— 共享计数器,证明同一实例
console.log(log1 === log2); // true
private constructor:TS 的 private 编译期阻止外部 new,强制只能通过 getInstance 获取实例,是单例的编译期保障。getInstance 调用时才创建,避免启动时实例化未用的对象;静态属性 instance 持有引用保证唯一。abstract class Singleton<T> + 静态 Map 复用单例逻辑,子类继承即获得 getInstance,避免每个单例类都重写一遍。this: new () => S 约束:静态方法的 this 参数约束调用方必须是可 new 的构造函数,让 Logger.getInstance() 返回精确的 Logger 类型而非基类。// 类型安全:getInstance 返回精确类型
const cfg = ConfigManager.getInstance();
cfg.set<number>('timeout', 3000);
const timeout = cfg.get<number>('timeout'); // number
// 继承单例基类的业务类
class Cache extends Singleton<Cache> {
private store = new Map<string, unknown>();
set(k: string, v: unknown) { this.store.set(k, v); }
get(k: string) { return this.store.get(k); }
}
const cache = Cache.getInstance(); // Cache 类型
cache.set('user', { id: 1 });
// 不同子类互不干扰,各自唯一
console.log(Logger.getInstance() === Cache.getInstance()); // 类型错误,不同类无法比较
console.log(Logger.getInstance() === Logger.getInstance()); // true
// 注意:单例在测试中是反模式,难以隔离状态
// 测试间需手动 reset 实例,否则用例互相污染