实现单例模式

手写 JavaScript 单例模式的多种实现方式

问题

实现一个单例模式 (Singleton),确保一个类只有一个实例,并提供全局访问点。

解答

闭包实现

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

  function createInstance(name) {
    return {
      name,
      getName() {
        return this.name;
      },
    };
  }

  return {
    getInstance(name) {
      // 如果实例不存在,创建一个
      if (!instance) {
        instance = createInstance(name);
      }
      // 返回已存在的实例
      return instance;
    },
  };
})();

// 测试
const a = Singleton.getInstance('first');
const b = Singleton.getInstance('second');

console.log(a === b); // true
console.log(a.getName()); // 'first'
console.log(b.getName()); // 'first'

ES6 Class 实现

class Singleton {
  static instance = null;

  constructor(name) {
    // 如果实例已存在,直接返回
    if (Singleton.instance) {
      return Singleton.instance;
    }
    this.name = name;
    Singleton.instance = this;
  }

  getName() {
    return this.name;
  }
}

// 测试
const a = new Singleton('first');
const b = new Singleton('second');

console.log(a === b); // true
console.log(a.getName()); // 'first'

Proxy 实现

function createSingleton(ClassName) {
  let instance = null;

  return new Proxy(ClassName, {
    construct(target, args) {
      if (!instance) {
        // 只在第一次调用时创建实例
        instance = new target(...args);
      }
      return instance;
    },
  });
}

// 普通类
class User {
  constructor(name) {
    this.name = name;
  }
}

// 转换为单例
const SingletonUser = createSingleton(User);

// 测试
const a = new SingletonUser('first');
const b = new SingletonUser('second');

console.log(a === b); // true
console.log(a.name); // 'first'

模块化单例(ES Module)

// singleton.js
class Database {
  constructor() {
    this.connection = null;
  }

  connect(url) {
    if (!this.connection) {
      this.connection = url;
      console.log(`Connected to ${url}`);
    }
    return this.connection;
  }
}

// ES Module 天然单例,导出的是同一个实例
export default new Database();

// 使用
// import db from './singleton.js'
// db.connect('mongodb://localhost')

关键点

  • 单例模式确保一个类只有一个实例,多次调用返回同一对象
  • 闭包实现通过私有变量 instance 保存实例
  • Class 实现利用静态属性存储实例,constructor 中判断并返回
  • Proxy 实现可以将任意类转换为单例,不侵入原有代码
  • ES Module 导出的对象天然是单例,模块只会执行一次