← 返回导航

单例模式

设计模式 · 实战

场景说明

单例模式保证一个类全局只有一个实例,常用于全局配置、缓存、日志、数据库连接池等只需一份的场景。前端典型应用是全局状态管理(如 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

关键点解析

使用示例与类型推导


      
// 类型安全: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 实例,否则用例互相污染
在 Playground 中尝试