实现单例模式
手写实现单例模式,确保一个类只有一个实例,并提供全局访问点
问题
单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在前端开发中,单例模式常用于管理全局状态、缓存、弹窗管理器等场景。需要实现一个单例模式,确保无论创建多少次,返回的都是同一个实例。
解答
方法一: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 的私有字段(#)提供了真正的私有性,防止外部直接访问实例
-
应用场景:全局状态管理、缓存管理、日志记录器、配置管理、弹窗管理器等需要全局唯一实例的场景
目录