fe-interview前端观察者模式:发布订阅与事件驱动
引言:为什么前端开发需要观察者模式?
在前端开发中,组件间的通信是一个永恒的话题。你是否曾经遇到过这样的困境:
- 父子组件需要传递数据,但层级过深导致props drilling问题
- 非父子组件需要共享状态,但又不适合使用全局状态管理
- 多个独立模块需要响应同一个事件变化
- 需要实现松耦合的组件间通信机制
观察者模式(Observer Pattern)正是解决这些问题的利器!作为前端面试中的高频考点,掌握观察者模式不仅能让你在面试中脱颖而出,更能提升你的架构设计能力。
观察者模式 vs 发布订阅模式:概念辨析
观察者模式(Observer Pattern)
观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
发布订阅模式(Pub-Sub Pattern)
发布订阅模式是观察者模式的变体,它通过一个消息代理(Message Broker)来解耦发布者和订阅者。
核心区别对比
| 特性 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 耦合度 | 相对紧密,直接依赖 | 完全解耦,通过中间件 |
| 通信方式 | 直接调用观察者方法 | 通过事件通道传递消息 |
| 灵活性 | 相对固定 | 高度灵活,动态订阅 |
| 典型应用 | Vue响应式系统 | EventBus、Redux |
JavaScript中的观察者模式实现
基础实现版本
class Subject {
constructor() {
this.observers = [];
this.state = null;
}
// 添加观察者
attach(observer) {
const isExist = this.observers.includes(observer);
if (!isExist) {
this.observers.push(observer);
}
}
// 移除观察者
detach(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
// 通知所有观察者
notify() {
console.log('Subject: Notifying observers...');
this.observers.forEach(observer => observer.update(this));
}
// 业务逻辑方法
someBusinessLogic() {
console.log('Subject: I\'m doing something important.');
this.state = Math.floor(Math.random() * 10);
console.log(`Subject: My state has just changed to: ${this.state}`);
this.notify();
}
}
class ConcreteObserver {
update(subject) {
if (subject.state < 3) {
console.log('ConcreteObserver: Reacted to the event.');
}
}
}
// 使用示例
const subject = new Subject();
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();
subject.attach(observer1);
subject.attach(observer2);
subject.someBusinessLogic();
subject.someBusinessLogic();
subject.detach(observer2);
subject.someBusinessLogic();
高级实现:支持多种事件类型
class AdvancedSubject {
constructor() {
this.events = {};
}
// 订阅事件
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return () => this.off(event, callback);
}
// 取消订阅
off(event, callback) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
// 发布事件
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Error in ${event} handler:`, error);
}
});
}
// 一次性订阅
once(event, callback) {
const onceCallback = (data) => {
callback(data);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
// 使用示例
const eventSystem = new AdvancedSubject();
// 订阅事件
const unsubscribe = eventSystem.on('dataChange', (data) => {
console.log('Data changed:', data);
});
// 发布事件
eventSystem.emit('dataChange', { newData: 'test' });
// 取消订阅
unsubscribe();
Vue中的观察者模式实践
Vue响应式原理核心
Vue的响应式系统是基于观察者模式的经典实现:
// 简化的Vue响应式原理
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 响应式对象实现
const reactiveHandlers = {
get(target, key, receiver) {
const dep = getDep(target, key);
dep.depend();
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const dep = getDep(target, key);
const result = Reflect.set(target, key, value, receiver);
dep.notify();
return result;
}
};
const targetToHashMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetToHashMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetToHashMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
function reactive(raw) {
return new Proxy(raw, reactiveHandlers);
}
// 使用示例
const state = reactive({ count: 0 });
watchEffect(() => {
console.log('Count:', state.count);
});
state.count++; // 自动触发重新执行
Vue EventBus实现
// 全局事件总线
class VueEventBus {
constructor() {
this.eventBus = new AdvancedSubject();
}
$on(event, callback) {
return this.eventBus.on(event, callback);
}
$off(event, callback) {
this.eventBus.off(event, callback);
}
$emit(event, data) {
this.eventBus.emit(event, data);
}
$once(event, callback) {
this.eventBus.once(event, callback);
}
}
// 在Vue中的使用
const EventBus = new VueEventBus();
// 组件A - 发布事件
EventBus.$emit('user-login', { userId: 123, userName: 'John' });
// 组件B - 订阅事件
EventBus.$on('user-login', (userInfo) => {
console.log('User logged in:', userInfo);
// 更新组件状态或执行其他操作
});
React中的观察者模式应用
使用Context API实现观察者模式
import React, { createContext, useContext, useReducer, useEffect } from 'react';
// 创建Context
const StoreContext = createContext();
// 状态 reducer
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_USER':
return { ...state, user: action.payload };
default:
return state;
}
}
// Provider组件
export function StoreProvider({ children }) {
const [state, dispatch] = useReducer(reducer, {
count: 0,
user: null
});
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
}
// 自定义Hook - 观察特定状态变化
export function useStore(selector = state => state) {
const { state, dispatch } = useContext(StoreContext);
const [selectedState, setSelectedState] = React.useState(selector(state));
useEffect(() => {
setSelectedState(selector(state));
}, [state, selector]);
return [selectedState, dispatch];
}
// 使用示例
function Counter() {
const [count, dispatch] = useStore(state => state.count);
return (
<div>
Count: {count}
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
}
function UserProfile() {
const [user] = useStore(state => state.user);
return (
<div>
{user ? `Welcome, ${user.name}` : 'Please login'}
</div>
);
}
使用自定义Hook实现发布订阅
import { useCallback, useEffect, useRef } from 'react';
function useEventEmitter() {
const subscribers = useRef(new Set());
const subscribe = useCallback((callback) => {
subscribers.current.add(callback);
return () => subscribers.current.delete(callback);
}, []);
const emit = useCallback((data) => {
subscribers.current.forEach(callback => callback(data));
}, []);
return { subscribe, emit };
}
// 使用示例
function ComponentA() {
const { emit } = useEventEmitter();
const handleClick = () => {
emit({ type: 'BUTTON_CLICKED', timestamp: Date.now() });
};
return <button onClick={handleClick}>Click me</button>;
}
function ComponentB() {
const { subscribe } = useEventEmitter();
useEffect(() => {
const unsubscribe = subscribe((event) => {
if (event.type === 'BUTTON_CLICKED') {
console.log('Button was clicked at:', event.timestamp);
}
});
return unsubscribe;
}, [subscribe]);
return <div>Listening for events...</div>;
}
实际应用场景与最佳实践
场景1:表单验证的观察者模式
class FormValidator {
constructor() {
this.fields = new Map();
this.validators = new Map();
this.errors = new Map();
}
// 注册字段
registerField(fieldName, validators = []) {
this.fields.set(fieldName, '');
this.validators.set(fieldName, validators);
this.errors.set(fieldName, []);
}
// 字段值变化
setFieldValue(fieldName, value) {
this.fields.set(fieldName, value);
this.validateField(fieldName);
}
// 验证单个字段
validateField(fieldName) {
const value = this.fields.get(fieldName);
const validators = this.validators.get(fieldName);
const errors = [];
validators.forEach(validator => {
const error = validator(value);
if (error) errors.push(error);
});
this.errors.set(fieldName, errors);
this.notifyValidationChange(fieldName, errors);
}
// 通知验证状态变化
notifyValidationChange(fieldName, errors) {
// 这里可以触发UI更新或其他操作
console.log(`Field ${fieldName} validation:`, errors);
}
// 获取表单验证状态
getFormStatus() {
let isValid = true;
this.errors.forEach(errors => {
if (errors.length > 0) isValid = false;
});
return isValid;
}
}
// 使用示例
const validator = new FormValidator();
// 注册字段和验证规则
validator.registerField('username', [
value => !value ? 'Username is required' : null,
value => value && value.length < 3 ? 'Username too short' : null
]);
validator.registerField('email', [
value => !value ? 'Email is required' : null,
value => value && !value.includes('@') ? 'Invalid email format' : null
]);
// 模拟用户输入
validator.setFieldValue('username', 'ab'); // 触发验证
validator.setFieldValue('email', 'test@example.com'); // 触发验证
场景2:实时数据同步
class DataSyncManager {
constructor() {
this.dataStores = new Map();
this.syncHandlers = new Map();
this.isSyncing = false;
}
// 注册数据存储
registerStore(storeName, initialData = {}) {
this.dataStores.set(storeName, initialData);
this.syncHandlers.set(storeName, new Set());
}
// 订阅数据变化
subscribe(storeName, callback) {
if (!this.syncHandlers.has(storeName)) {
throw new Error(`Store ${storeName} not registered`);
}
this.syncHandlers.get(storeName).add(callback);
return () => this.unsubscribe(storeName, callback);
}
unsubscribe(storeName, callback) {
const handlers = this.syncHandlers.get(storeName);
if (handlers) {
handlers.delete(callback);
}
}
// 更新数据并通知
updateData(storeName, newData) {
if (!this.dataStores.has(storeName)) {
throw new Error(`Store ${storeName} not registered`);
}
const oldData = this.dataStores.get(storeName);
this.dataStores.set(storeName, { ...oldData, ...newData });
// 通知所有订阅者
this.notifySubscribers(storeName, this.dataStores.get(storeName));
}
// 通知订阅者
notifySubscribers(storeName, data) {
const handlers = this.syncHandlers.get(storeName);
if (handlers) {
handlers.forEach(handler => {
try {
handler(data);
} catch (error) {
console.error('Error in sync handler:', error);
}
});
}
}
// 批量更新
batchUpdate(updates) {
this.isSyncing = true;
try {
Object.entries(updates).forEach(([storeName, data]) => {
this.updateData(storeName, data);
});
} finally {
this.isSyncing = false;
}
}
}
// 使用示例
const syncManager = new DataSyncManager();
// 注册数据存储
syncManager.registerStore('userPreferences', { theme: 'light', language: 'en' });
syncManager.registerStore('appState', { isOnline: true, isLoading: false });
// 订阅数据变化
syncManager.subscribe('userPreferences', (data) => {
console.log('User preferences updated:', data);
// 更新UI或执行其他操作
});
// 更新数据
syncManager.updateData('userPreferences', { theme: 'dark' });
性能优化与注意事项
内存泄漏防护
class SafeEventEmitter {
constructor() {
this.events = new Map();
this.weakRefs = new WeakMap();
}
on(event, callback, target) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
const handlers = this.events.get(event);
// 使用WeakRef避免内存泄漏
let callbackRef;
if (target) {
callbackRef = new WeakRef({ callback, target });
this.weakRefs.set(callback, callbackRef);
} else {
callbackRef = { callback };
}
handlers.add(callbackRef);
return () => this.off(event, callback);
}
off(event, callback) {
if (!this.events.has(event)) return;
const handlers = this.events.get(event);
for (const ref of handlers) {
const cb = ref instanceof WeakRef ? ref.deref()?.callback : ref.callback;
if (cb === callback) {
handlers.delete(ref);
break;
}
}
}
emit(event, data) {
if (!this.events.has(event)) return;
const handlers = this.events.get(event);
const toRemove = [];
for (const ref of handlers) {
let callback;
if (ref instanceof WeakRef) {
const obj = ref.deref();
if (!obj) {
toRemove.push(ref);
continue;
}
callback = obj.callback;
} else {
callback = ref.callback;
}
try {
callback(data);
} catch (error) {
console.error('Error in event handler:', error);
}
}
// 清理无效的WeakRef
toRemove.forEach(ref => handlers.delete(ref));
}
}
性能优化策略
- 防抖和节流处理
- 批量更新机制
- 选择性通知
- 懒加载监听器
class OptimizedObserver {
constructor() {
this.observers = new Map();
this.updateQueue = new Set();
this.isBatching = false;
}
// 批量更新
batchUpdate(callback) {
this.isBatching = true;
try {
callback();
} finally {
this.isBatching = false;
this.processQueue();
}
}
// 延迟通知
scheduleNotify(key) {
this.updateQueue.add(key);
if (!this.isBatching) {
this.processQueue();
}
}
processQueue() {
if (this.updateQueue.size === 0) return;
const queue = [...this.updateQueue];
this.updateQueue.clear();
queue.forEach(key => {
const observers = this.observers.get(key);
if (observers) {
observers.forEach(observer => observer());
}
});
}
// 添加观察者
observe(key, callback) {
if (!this.observers.has(key)) {
this.observers.set(key, new Set());
}
this.observers.get(key).add(callback);
return () => {
const observers = this.observers.get(key);
if (observers) {
observers.delete(callback);
if (observers.size === 0) {
this.observers.delete(key);
}
}
};
}
}
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



