1. 项目背景与核心价值
作为一个从C++转向前端开发的工程师,我深刻理解后端开发者在前端领域可能遇到的思维转换障碍。这个系列文章记录了我从零开始构建实时通信系统的完整过程,本篇重点探讨如何用观察者模式实现WebSocket订阅机制。
对于习惯C++面向对象思维的开发者来说,观察者模式是个非常熟悉的伙伴。但在前端领域实现它时,我们需要考虑浏览器环境的特殊性:单线程事件循环、异步回调机制、以及WebSocket连接的生命周期管理。这个实现方案完美融合了后端的设计模式与前端的运行环境特点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 观察者模式在前端的特殊实现
2.1 经典模式与前端适配
传统观察者模式通常包含三个核心组件:
- Subject(主题):维护观察者列表,提供注册/注销接口
- Observer(观察者):定义通知接口
- ConcreteObserver(具体观察者):实现具体响应逻辑
在前端实现时,我们需要做以下关键调整:
typescript复制interface WebSocketObserver {
update(data: any): void;
eventType: string; // 新增字段,用于区分不同事件类型
}
class WebSocketSubject {
private observers: Map<string, WebSocketObserver[]> = new Map();
// 注册时需指定关注的事件类型
attach(eventType: string, observer: WebSocketObserver) {
if (!this.observers.has(eventType)) {
this.observers.set(eventType, []);
}
this.observers.get(eventType)?.push(observer);
}
}
关键调整:传统观察者模式通常不区分事件类型,但在WebSocket场景下,我们需要支持多种消息类型的订阅,因此引入了事件类型区分机制。
2.2 连接状态管理
WebSocket连接有其特有的生命周期状态,我们需要扩展观察者模式来应对:
typescript复制enum ConnectionState {
CONNECTING,
OPEN,
CLOSING,
CLOSED,
RECONNECTING
}
class WebSocketSubject {
private state: ConnectionState = ConnectionState.CLOSED;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
// 状态变更时通知所有观察者
private notifyStateChange() {
Array.from(this.observers.values())
.flat()
.forEach(observer => {
if (typeof observer.onStateChange === 'function') {
observer.onStateChange(this.state);
}
});
}
}
3. 完整实现方案
3.1 WebSocket封装核心
typescript复制class WebSocketManager implements WebSocketSubject {
private socket: WebSocket | null = null;
private url: string;
private protocols?: string | string[];
constructor(url: string, protocols?: string | string[]) {
this.url = url;
this.protocols = protocols;
this.connect();
}
private connect() {
this.socket = new WebSocket(this.url, this.protocols);
this.setState(ConnectionState.CONNECTING);
this.socket.onopen = () => {
this.setState(ConnectionState.OPEN);
this.reconnectAttempts = 0;
};
this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.notify(data.type, data.payload);
} catch (e) {
console.error('Failed to parse WebSocket message', e);
}
