广告

JavaScript设计模式实现全解:单例模式与观察者模式的实战代码解析与应用场景

单例模式

概念与原理

在软件设计中,单例模式旨在确保一个类只有一个实例,并提供一个全局访问点。它通过将构造函数私有化、使用工厂函数或模块化实现来实现,避免重复创建资源。最关键点是实例的唯一性和全局可访问性,从而在复杂应用中稳定地共享状态。

在JavaScript领域,闭包立即执行函数表达式(IIFE)经常被用来实现单例,确保外部无法直接通过new创建新实例。通过封装内部状态,外部只能通过受控方法访问,从而实现数据的保护和一致性。

JavaScript实现方式

下面给出几种常见的JavaScript单例模式实现:模块模式、惰性初始化与懒加载等,以便在不同场景快速落地。

// 模块模式单例(JavaScript模块化环境)
// 直接暴露一个对象,内部状态对外部只读或受控
const Singleton = (function () {let instance;function init() {// 私有属性和方法const _privateState = { count: 0 };function _privateMethod() { return _privateState.count; }// 对外暴露的公有方法return {increment: function () { _privateState.count += 1; },getCount: function () { return _privateState.count; },reset: function () { _privateState.count = 0; }};}return {getInstance: function () {if (!instance) {instance = init();}return instance;}};
})();const a = Singleton.getInstance();
a.increment();
console.log(a.getCount()); // 1const b = Singleton.getInstance();
console.log(b.getCount()); // 1 - 同一个实例

此外,懒加载单例在首次需要时才创建实例,避免初始加载成本。下面是classprivate字段实现的示例,适用于现代浏览器环境。

// 懒加载单例(class + 静态私有字段,适用于现代浏览器) 
class SingletonLazy {constructor() {if (SingletonLazy._instance) {return SingletonLazy._instance;}this._data = { initialized: true };SingletonLazy._instance = this;}static getInstance() {if (!SingletonLazy._instance) {SingletonLazy._instance = new SingletonLazy();}return SingletonLazy._instance;}
}
const s1 = SingletonLazy.getInstance();
const s2 = SingletonLazy.getInstance();
console.log(s1 === s2); // true

观察者模式

概念与结构

观察者模式是一种发布-订阅模式,允许对象在状态改变时通知一组观察者。核心要点是主题(被观察者)维护观测者列表、并在事件发生时触发回调,从而实现解耦的事件驱动架构。

在前端应用中,事件驱动架构经常依赖观察者模式来实现组件解耦、事件广播和跨组件通信。通过建立一个清晰的订阅-发布机制,所有感兴趣的部件都能在事件发生时获知并作出响应。

实现方式与事件总线

以下实现展示了一个简易的事件总线(EventBus),支持注册、取消、触发和一次性监听。强烈建议在大型应用中使用成熟库以提升性能和健壮性。

// 简易事件总线(Observer模式)
class EventBus {constructor() {this.events = {};}on(event, listener) {if (!this.events[event]) this.events[event] = [];this.events[event].push(listener);}off(event, listener) {if (!this.events[event]) return;this.events[event] = this.events[event].filter(l => l !== listener);}emit(event, data) {if (!this.events[event]) return;this.events[event].forEach(listener => listener(data));}
}

在实战中,订阅甄别回调签名的统一化能显著降低耦合度。下面给出一个常见的用法示例,说明事件总线在跨模块通信中的作用。

const bus = new EventBus();
bus.on('configChanged', (config) => {console.log('接收到新配置', config);
});
bus.emit('configChanged', { theme: 'dark' });

实战代码解析与应用场景

单例模式在配置管理中的应用

配置管理通常需要一份全局的、不可变的配置对象,单例模式提供全局访问点,确保各模块读取到的一致配置版本。通过对配置对象进行只读封装,可以防止随意修改,提升应用的稳定性。

下面的代码演示了一个只读配置的单例实现,允许初始化一次后再读取但不可直接修改,从而实现安全的全局配置读取。

// 全局配置单例:只读对象
const ConfigSingleton = (function () {let instance;function init(initialConfig) {const config = Object.freeze({ ...initialConfig });return {get: (k) => config[k],getAll: () => config};}return {initIfNeeded: function (config) {if (!instance && config) {instance = init(config);}return instance;}// 如果需要强制刷新,则暴露一个重新初始化的接口(谨慎使用),refresh: function (config) {if (config) { instance = init(config); }return instance;}};
})();const cfg = ConfigSingleton.initIfNeeded({ theme: 'dark', language: 'cn' });
console.log(cfg.get('theme'));

观察者模式在界面事件通信中的应用

跨组件通信常常需要解耦,观察者模式提供了一个事件中心,让不同组件订阅关心的事件并作出响应。通过事件总线实现,组件A发布事件,组件B订阅事件后就能即时处理,显著提升应用的可维护性和扩展性。

在实际项目中,事件中心的命名规范订阅生命周期管理至关重要,避免内存泄漏与冗余回调。以下示例展示了如何结合事件总线完成跨组件通信。

JavaScript设计模式实现全解:单例模式与观察者模式的实战代码解析与应用场景

// 使用事件总线实现跨组件通信示例
// 假设 bus 已创建
bus.on('userLoggedIn', user => {// 更新 UI、加载数据等console.log('欢迎', user.name);
});
bus.emit('userLoggedIn', { name: 'Alice' });

性能与实现注意

资源泄漏与闭包管理

在单例模式实现中,闭包和全局引用需要小心清理,以避免内存泄漏。合理使用短生命周期对象、弱引用监控以及按需销毁可以降低风险,特别是在需要多实例并发时要注意对资源的释放。

在观察者模式中,避免无限订阅、及时清理订阅以及对不再使用的对象解除绑定是关键,事件清理机制应成为设计的一部分,避免长期挂钩导致的性能下降。

模块化与兼容性

前端开发中,模块化加载器(如ES6模块、CommonJS、AMD等)决定了单例/观察者的实现方式。在不同环境中,需要考虑模块边界、按需加载与热重载的影响,以及在混合环境下的兼容性问题。

广告