fe-interview前端观察者模式:发布订阅与事件驱动

fe-interview前端观察者模式:发布订阅与事件驱动

【免费下载链接】fe-interview haizlin/fe-interview: 前端面试指南,包含大量的前端面试题及参考答案,适合用于准备前端面试。 【免费下载链接】fe-interview 项目地址: https://gitcode.com/GitHub_Trending/fe/fe-interview

引言:为什么前端开发需要观察者模式?

在前端开发中,组件间的通信是一个永恒的话题。你是否曾经遇到过这样的困境:

  • 父子组件需要传递数据,但层级过深导致props drilling问题
  • 非父子组件需要共享状态,但又不适合使用全局状态管理
  • 多个独立模块需要响应同一个事件变化
  • 需要实现松耦合的组件间通信机制

观察者模式(Observer Pattern)正是解决这些问题的利器!作为前端面试中的高频考点,掌握观察者模式不仅能让你在面试中脱颖而出,更能提升你的架构设计能力。

观察者模式 vs 发布订阅模式:概念辨析

观察者模式(Observer Pattern)

观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。

mermaid

发布订阅模式(Pub-Sub Pattern)

发布订阅模式是观察者模式的变体,它通过一个消息代理(Message Broker)来解耦发布者和订阅者。

mermaid

核心区别对比

特性观察者模式发布订阅模式
耦合度相对紧密,直接依赖完全解耦,通过中间件
通信方式直接调用观察者方法通过事件通道传递消息
灵活性相对固定高度灵活,动态订阅
典型应用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));
  }
}

性能优化策略

  1. 防抖和节流处理
  2. 批量更新机制
  3. 选择性通知
  4. 懒加载监听器
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);
        }
      }
    };
  }
}

【免费下载链接】fe-interview haizlin/fe-interview: 前端面试指南,包含大量的前端面试题及参考答案,适合用于准备前端面试。 【免费下载链接】fe-interview 项目地址: https://gitcode.com/GitHub_Trending/fe/fe-interview

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值