实现单例模式

手写实现单例模式,确保一个类只有一个实例,并提供全局访问点

问题

单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在前端开发中,单例模式常用于管理全局状态、缓存、弹窗管理器等场景。需要实现一个单例模式,确保无论创建多少次,返回的都是同一个实例。

解答

方法一:ES6 Class 实现(推荐)

class Singleton {
  // 使用静态私有属性存储实例
  static #instance = null;

  constructor(name) {
    // 如果实例已存在,直接返回
    if (Singleton.#instance) {
      return Singleton.#instance;
    }
    
    // 初始化实例属性
    this.name = name;
    this.timestamp = Date.now();
    
    // 保存实例
    Singleton.#instance = this;
  }

  // 静态方法获取实例
  static getInstance(name) {
    if (!Singleton.#instance) {
      Singleton.#instance = new Singleton(name);
    }
    return Singleton.#instance;
  }

  // 实例方法
  getName() {
    return this.name;
  }
}

方法二:闭包实现

const Singleton = (function() {
  let instance = null;

  class SingletonClass {
    constructor(name) {
      if (instance) {
        return instance;
      }
      this.name = name;
      this.timestamp = Date.now();
      instance = this;
    }

    getName() {
      return this.name;
    }
  }

  return SingletonClass;
})();

方法三:通用单例装饰器

// 将任意类转换为单例
function singleton(ClassName) {
  let instance = null;
  
  return new Proxy(ClassName, {
    construct(target, args) {
      if (!instance) {
        instance = new target(...args);
      }
      return instance;
    }
  });
}

// 使用装饰器
class Database {
  constructor(config) {
    this.config = config;
  }
  
  connect() {
    console.log('连接数据库:', this.config);
  }
}

const SingletonDatabase = singleton(Database);

方法四:惰性单例(按需创建)

const lazySingleton = (function() {
  let instance = null;

  function init(name) {
    // 真正的初始化逻辑
    return {
      name: name,
      timestamp: Date.now(),
      getName() {
        return this.name;
      }
    };
  }

  return {
    getInstance(name) {
      if (!instance) {
        instance = init(name);
      }
      return instance;
    }
  };
})();

使用示例

// 示例1:基本使用
const instance1 = new Singleton('第一次创建');
const instance2 = new Singleton('第二次创建');

console.log(instance1 === instance2); // true
console.log(instance1.getName()); // '第一次创建'
console.log(instance2.getName()); // '第一次创建'

// 示例2:使用静态方法
const instance3 = Singleton.getInstance('静态方法创建');
console.log(instance1 === instance3); // true

// 示例3:实际应用 - 全局状态管理
class GlobalStore {
  static #instance = null;

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

  setState(key, value) {
    this.state[key] = value;
  }

  getState(key) {
    return this.state[key];
  }
}

const store1 = new GlobalStore();
store1.setState('user', { name: 'Alice' });

const store2 = new GlobalStore();
console.log(store2.getState('user')); // { name: 'Alice' }

// 示例4:弹窗管理器
class ModalManager {
  static #instance = null;

  constructor() {
    if (ModalManager.#instance) {
      return ModalManager.#instance;
    }
    this.modals = [];
    ModalManager.#instance = this;
  }

  open(modalConfig) {
    this.modals.push(modalConfig);
    console.log('打开弹窗:', modalConfig.title);
  }

  close() {
    const modal = this.modals.pop();
    console.log('关闭弹窗:', modal?.title);
  }

  getActiveModals() {
    return this.modals;
  }
}

const modalMgr1 = new ModalManager();
modalMgr1.open({ title: '登录弹窗' });

const modalMgr2 = new ModalManager();
console.log(modalMgr2.getActiveModals()); // [{ title: '登录弹窗' }]

关键点

  • 唯一实例保证:通过静态私有属性或闭包变量存储唯一实例,确保全局只有一个对象

  • 构造函数检查:在构造函数中检查实例是否已存在,存在则直接返回,避免重复创建

  • 静态方法访问:提供 getInstance() 静态方法作为获取实例的统一入口,更符合设计模式规范

  • 延迟初始化:惰性单例模式在真正需要时才创建实例,节省资源

  • 线程安全:JavaScript 是单线程的,不需要考虑多线程并发问题,但在 Node.js 等环境需注意异步场景

  • Proxy 代理:使用 Proxy 可以创建通用的单例装饰器,将任意类转换为单例模式

  • 私有属性:ES2022 的私有字段(#)提供了真正的私有性,防止外部直接访问实例

  • 应用场景:全局状态管理、缓存管理、日志记录器、配置管理、弹窗管理器等需要全局唯一实例的场景