1. 单例模式(Singleton)

问题:全局只需要一个实例(如配置、缓存、数据库连接)。

核心:用一个变量标记是否已创建过实例,如果有就直接返回。

class Singleton {
  static #instance: Singleton | null = null;

  constructor() {
    if (Singleton.#instance) {
      return Singleton.#instance;
    }
    Singleton.#instance = this;
  }
}

const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true
package singleton

import "sync"

type singleton struct{}

var (
  instance *singleton
  once     sync.Once
)

func GetInstance() *singleton {
  once.Do(func() {
    instance = &singleton{}
  })
  return instance
}

实战场景

  • 全局配置中心:应用启动时读取一次环境变量、远程配置或构建配置,后续模块通过同一个配置对象读取,避免每个模块重复解析。
  • 日志实例:服务端常见的 loggertracermetrics 客户端通常全局共享,方便统一格式、采样率、上下文字段和输出目的地。
  • 数据库连接池:真正共享的通常不是单个连接,而是连接池实例。业务代码都从同一个池里取连接,避免重复创建昂贵资源。
  • 浏览器端 SDK:埋点、A/B 实验、Feature Flag、错误上报 SDK 初始化一次即可,页面组件只复用同一个客户端。
  • 缓存管理器:内存缓存、LRU 缓存或本地缓存封装成单例,可以让多个模块共享同一份缓存状态。
  • i18n 资源管理:语言包加载器、当前语言状态、格式化函数可以集中在一个实例里,避免页面各处各自维护语言状态。
  • 依赖注入容器:后端服务或前端大型应用中,容器本身常常是单例,负责保存各种 service 的创建和生命周期。
  • WebSocket 连接管理:聊天、协作编辑、实时通知类应用通常需要复用一个连接管理器,避免同一页面重复建立多个连接。
  • 命令行工具上下文:CLI 启动后创建一个全局运行上下文,保存参数、输出器、配置和临时目录,后续命令共享它。
  • 需要克制的地方:单例很容易变成隐藏的全局可变状态。适合放“确实全局唯一”的基础设施,不适合承载随请求、随用户变化的业务数据。

在 JS 中,模块本身天然就是单例——import 同一个模块,拿到的始终是同一个对象。