一、模式定義與核心價值
單例模式(Singleton Pattern)是一種創建型設計模式,保證一個類僅有一個實例,并提供全局訪問點。其核心價值在于:
- ??資源控制??:避免重復創建消耗性資源(如數據庫連接)
- ??狀態共享??:維護全局唯一狀態(如應用配置)
- ??訪問管控??:集中管理共享資源訪問(如日志系統)
二、經典實現方案對比
1. 閉包實現(ES5)
const Singleton = (() => {let instance = null;function createInstance() {// 私有方法和屬性const privateMethod = () => console.log('Private method');let privateVar = 'Initial value';return {// 暴露的公共接口publicMethod: () => {privateMethod();console.log('Public method called');},getVar: () => privateVar,setVar: (value) => { privateVar = value }};}return {getInstance: () => {if (!instance) {instance = createInstance();}return instance;}};
})();// 使用示例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
2. 類靜態屬性(ES6+)
class DatabaseConnection {static instance = null;connectionCount = 0;constructor() {if (DatabaseConnection.instance) {return DatabaseConnection.instance;}// 模擬耗時的連接初始化this.connectionCount = 0;DatabaseConnection.instance = this;}connect() {this.connectionCount++;console.log(`Active connections: ${this.connectionCount}`);}disconnect() {this.connectionCount = Math.max(0, this.connectionCount - 1);}
}// 使用示例
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
db1.connect(); // Active connections: 1
db2.connect(); // Active connections: 2
console.log(db1 === db2); // true
3. 模塊模式(現代ES Module)
// config.js
let configInstance = null;export default class AppConfig {constructor() {if (!configInstance) {this.env = process.env.NODE_ENV || 'development';this.apiBase = this.env === 'production' ? 'https://api.example.com' : 'http://localhost:3000';configInstance = this;}return configInstance;}// 添加配置凍結防止修改freeze() {Object.freeze(this);}
}// 初始化并凍結配置
const config = new AppConfig();
config.freeze();
三、高級應用場景
1. 帶生命周期的單例
class SessionManager {static instance = null;static getInstance() {if (!this.instance) {this.instance = new SessionManager();// 注冊頁面卸載清理window.addEventListener('beforeunload', () => {this.instance.cleanup();});}return this.instance;}constructor() {this.sessions = new Map();this.timeouts = new Map();}createSession(userId, ttl = 3600) {const sessionId = crypto.randomUUID();this.sessions.set(sessionId, { userId, created: Date.now() });// 自動過期處理this.timeouts.set(sessionId, setTimeout(() => {this.destroySession(sessionId);}, ttl * 1000));return sessionId;}destroySession(sessionId) {clearTimeout(this.timeouts.get(sessionId));this.sessions.delete(sessionId);this.timeouts.delete(sessionId);}cleanup() {this.timeouts.forEach(clearTimeout);this.sessions.clear();this.timeouts.clear();}
}// 使用示例
const sessionManager = SessionManager.getInstance();
const sessionId = sessionManager.createSession('user123');
四、實踐建議與注意事項
1. 合理使用場景
? 適用場景:
- 全局狀態管理(Redux/Vuex Store)
- 瀏覽器環境唯一對象(如全屏加載器)
- 共享資源訪問(IndexedDB連接池)
? 避免濫用:
- 普通工具類(應使用純函數)
- 短期使用的上下文對象(如表單數據)
- 需要多實例的場景(如彈窗工廠)
2. 性能優化技巧
class OptimizedSingleton {static #instance; // 私有字段static #initialized = false;constructor() {if (!OptimizedSingleton.#initialized) {throw new Error('Use getInstance() method');}// 初始化邏輯...}static getInstance() {if (!this.#instance) {this.#initialized = true;this.#instance = new OptimizedSingleton();this.#initialized = false;}return this.#instance;}
}
3. 測試友好方案
// 可重置的單例模式
class TestableService {static instance;static reset() {this.instance = null;}constructor() {if (TestableService.instance) {return TestableService.instance;}// 初始化邏輯...TestableService.instance = this;}
}// 測試用例示例
describe('Service Test', () => {afterEach(() => {TestableService.reset();});test('instance equality', () => {const a = new TestableService();const b = new TestableService();expect(a).toBe(b);});
});
五、常見陷阱與解決方案
- ??模塊熱替換問題??
// 熱模塊替換兼容方案
if (module.hot) {module.hot.dispose(() => {Singleton.cleanup();});module.hot.accept();
}
- ??多窗口場景處理??
// 使用BroadcastChannel實現跨窗口單例
class CrossTabSingleton {static instance;static EVENT_KEY = 'singleton-update';constructor() {this.channel = new BroadcastChannel(CrossTabSingleton.EVENT_KEY);this.channel.onmessage = (event) => {if (event.data === 'instance-created') {// 處理其他頁面實例化的情況}};}static getInstance() {if (!this.instance) {this.instance = new CrossTabSingleton();this.instance.channel.postMessage('instance-created');}return this.instance;}
}
- ??內存泄漏預防??
class LeakSafeSingleton {static #weakRef;static getInstance() {let instance = this.#weakRef?.deref();if (!instance) {instance = new LeakSafeSingleton();this.#weakRef = new WeakRef(instance);// 注冊清理回調this.#registerFinalizer(instance);}return instance;}static #registerFinalizer(instance) {const registry = new FinalizationRegistry(() => {// 實例被GC回收后的處理console.log('Instance cleaned up');});registry.register(instance, 'instance');}
}
六、架構層面的思考
- ??依賴注入整合??
interface IService {operation(): void;
}class RealService implements IService {operation() {console.log('Real operation');}
}class SingletonService {private static instance: IService;static provide(impl?: new () => IService) {if (!this.instance) {this.instance = impl ? new impl() : new RealService();}return this.instance;}
}// 在應用入口
const service = SingletonService.provide();// 測試時可注入mock實現
class MockService implements IService {operation() {console.log('Mock operation');}
}
SingletonService.provide(MockService);
- ??微前端架構下的單例管理??
class FederatedSingleton {static instances = new Map();static register(name, instance) {if (!this.instances.has(name)) {this.instances.set(name, instance);}}static get(name) {if (!this.instances.has(name)) {throw new Error(`Instance ${name} not registered`);}return this.instances.get(name);}
}// 主應用注冊
FederatedSingleton.register('authService', new AuthService());// 子應用使用
const authService = FederatedSingleton.get('authService');
建議
-
??模式選擇策略??:
- 簡單場景:使用模塊導出方案
- 復雜生命周期:類靜態屬性實現
- 測試需求:支持重置的變體
-
??性能考量??:
- 高頻訪問場景使用直接對象訪問
- 大數據量場景使用惰性加載
-
??架構演進??:
- 預留擴展點(如二次初始化方法)
- 考慮可能的集群化擴展需求
正確應用單例模式能夠有效管理系統中的特殊資源,但需要警惕其成為全局狀態污染的源頭。
在現代化前端架構中,建議結合DI容器或狀態管理庫使用,保持核心業務邏輯的純凈性。