您现在的位置是:网站首页 > 单例模式(Singleton)的多种实现方式文章详情

单例模式(Singleton)的多种实现方式

单例模式是一种常用的设计模式,确保一个类只有一个实例,并提供全局访问点。在JavaScript中,实现单例的方式灵活多样,可以通过闭包、类、模块化等手段达成目标。

基础实现:静态属性方式

最简单的单例实现是通过类的静态属性存储实例。当首次调用时创建实例并缓存,后续调用直接返回缓存的实例。

class Singleton {
  static instance = null;

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

  log() {
    console.log('Singleton instance');
  }
}

const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // true

这种方式在ES6类语法中清晰直观,但静态属性属于类而非实例,可能被意外修改。

闭包实现:IIFE方式

利用立即执行函数(IIFE)和闭包特性,可以创建完全私有的单例实例,避免外部访问和修改。

const Singleton = (function() {
  let instance;

  function createInstance() {
    return {
      log() {
        console.log('Private singleton');
      }
    };
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true

这种实现完全隐藏了实例创建细节,通过暴露的getInstance方法控制访问,是经典的实现方式。

模块化实现:ES6模块

现代JavaScript的模块系统天然支持单例模式。模块在首次导入时执行且只执行一次,导出的对象自然成为单例。

// singleton.js
let instance;

export default {
  log() {
    console.log('Module singleton');
  }
};

// main.js
import Singleton from './singleton.js';
import Singleton2 from './singleton.js';

console.log(Singleton === Singleton2); // true

模块化实现最为简洁,但需要构建工具支持。在Node.js环境或Webpack等打包工具中可以直接使用。

惰性单例:按需创建

某些场景下需要延迟实例化,直到真正需要时才创建实例。可以通过代理模式或getter拦截实现。

class HeavyResource {
  constructor() {
    console.log('Expensive operation');
  }
}

const LazySingleton = {
  get instance() {
    if (!this._instance) {
      this._instance = new HeavyResource();
    }
    return this._instance;
  }
};

// 直到访问instance属性时才创建实例
console.log('Before access');
const resource = LazySingleton.instance;

这种方式适合初始化成本高的资源,如数据库连接、大型配置文件加载等场景。

线程安全实现(模拟)

虽然JavaScript是单线程语言,但在某些异步场景下仍需考虑初始化竞态问题。可以通过锁机制模拟线程安全。

class SafeSingleton {
  static instance;
  static lock = false;

  static async getInstance() {
    if (this.instance) return this.instance;

    while (this.lock) {
      await new Promise(resolve => setTimeout(resolve, 10));
    }

    this.lock = true;
    try {
      if (!this.instance) {
        this.instance = new SafeSingleton();
      }
    } finally {
      this.lock = false;
    }

    return this.instance;
  }
}

// 并发测试
Promise.all([
  SafeSingleton.getInstance(),
  SafeSingleton.getInstance()
]).then(([a, b]) => {
  console.log(a === b); // true
});

单例注册表

当需要管理多个单例时,可以使用注册表模式集中管理。通过key-value映射存储不同类型的单例。

class SingletonRegistry {
  static instances = new Map();

  static getInstance(key, creator) {
    if (!this.instances.has(key)) {
      this.instances.set(key, creator());
    }
    return this.instances.get(key);
  }
}

const logger = SingletonRegistry.getInstance('logger', () => ({
  log: msg => console.log(msg)
}));

const api = SingletonRegistry.getInstance('api', () => ({
  fetch: () => Promise.resolve('data')
}));

破坏单例的防护

某些情况下需要防止单例被意外破坏,可以通过以下方式加固:

  1. 冻结对象防止修改
const strictSingleton = Object.freeze({
  method() {}
});
  1. 构造函数防重写
class ProtectedSingleton {
  constructor() {
    if (new.target !== ProtectedSingleton) {
      throw new Error('Use getInstance()');
    }
  }

  static getInstance() {
    if (!this._instance) {
      this._instance = new ProtectedSingleton();
    }
    return this._instance;
  }
}

单例与依赖注入

在现代前端框架中,单例常通过依赖注入容器管理。以下是模拟实现:

class Container {
  static bindings = new Map();

  static singleton(key, creator) {
    let instance;
    this.bindings.set(key, () => {
      if (!instance) instance = creator();
      return instance;
    });
  }

  static resolve(key) {
    const factory = this.bindings.get(key);
    return factory?.();
  }
}

// 注册单例服务
Container.singleton('logger', () => ({
  log: console.log
}));

// 获取实例
const logger = Container.resolve('logger');

单例模式的变体

  1. 多例模式(Multiton):限制实例数量而非单个
class Multiton {
  static instances = new Map();
  static MAX_INSTANCES = 3;

  static getInstance(key) {
    if (!this.instances.has(key)) {
      if (this.instances.size >= this.MAX_INSTANCES) {
        throw new Error('Instance limit reached');
      }
      this.instances.set(key, new Multiton());
    }
    return this.instances.get(key);
  }
}
  1. 环境隔离单例:根据不同环境(如开发/生产)返回不同实例
class EnvSingleton {
  static instances = {
    development: null,
    production: null
  };

  static getInstance(env) {
    if (!this.instances[env]) {
      this.instances[env] = new EnvSingleton(env);
    }
    return this.instances[env];
  }

  constructor(env) {
    this.env = env;
  }
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步