zhangdizhangdi

单例模式 ⭐

定义

单例模式(Singleton Pattern),调用一个类,任何时候返回的都是同一个实例。

  • 一个类只有一个实例
  • 可以全局访问

“单例模式虽然简单好用,但它违背了单一职责原则(SRP)。因为它既要负责自己的核心业务逻辑,又要负责控制自己的实例化生命周期,导致测试起来非常困难(容易产生全局状态污染)。
所以在现代如 Angular 或 NestJS 等高级框架中,我们不再手写单例,而是写普通的类,然后依靠依赖注入(DI)和 IoC 容器来替我们管理单例的生命周期,这才是更优雅的设计。”

应用场景

  • 弹窗、登录框
  • 购物车
  • 状态管理(Vuex/Pinia/Redux)
  • Axios 请求实例
  • EventBus
js
// http.js
import axios from 'axios';

// create 方法内部其实就是配置好并返回了一个单例
const httpInstance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000
});

httpInstance.interceptors.request.use(...);

// 导出这个唯一的实例,全项目共用
export default httpInstance;

实现

1. 基于 ES6 Class 和 static

js
class GameManager {
  // 静态属性,用于保存唯一的实例
  static instance = null;

  constructor(name) {
    // 核心拦截逻辑:如果实例已经存在,直接返回旧实例,阻止新实例的创建
    if (GameManager.instance) {
      return GameManager.instance;
    }
    
    this.name = name;
    this.score = 0;
    // 将当前实例保存到静态属性上
    GameManager.instance = this;
  }

  // 也可以提供一个标准的静态获取方法
  static getInstance(name) {
    if (!this.instance) {
      this.instance = new GameManager(name);
    }
    return this.instance;
  }
}

// 测试一下:
const game1 = new GameManager('玩家A');
const game2 = new GameManager('玩家B');

console.log(game1 === game2); // true!它们是同一个对象
console.log(game2.name); // 打印 "玩家A"。(玩家B并没有创建成功)

2. 基于闭包的“透明单例”

js
const SingletonLogger = (function () {
  let instance = null; // 闭包里的私有变量,用来保存实例

  // 真正的构造函数
  return function Logger(name) {
    if (instance) {
      return instance; // 如果有了,直接返回
    }
    this.name = name;
    instance = this; // 第一次执行时,赋值给闭包变量
    return this;
  };
})();

const logger1 = new SingletonLogger('log1');
const logger2 = new SingletonLogger('log2');
console.log(logger1 === logger2); // true

“懒汉式” vs “饿汉式”

  • 饿汉式(Eager): 只要文件一加载(类一被声明),马上就把实例创建出来。“我很饿,提前准备好吃的”。
  • 懒汉式(Lazy): 文件加载时不创建实例,直到代码第一次调用 getInstance() 或者 new 的时候,才去创建。“我很懒,你推我一下我才动”。