发布订阅模式:从EventEmitter到复杂事件系统,前端工程师必会的观察者模式
🧑🏫 作者:全栈老李
📅 更新时间:2025 年 5 月
🧑💻 适合人群:前端初学者、进阶开发者
🚀 版权:本文由全栈老李原创,转载请注明出处。
今天咱们聊聊前端开发中那个无处不在却又容易被忽视的设计模式——发布订阅模式。我是全栈老李,一个喜欢把复杂技术讲简单的技术博主。
发布订阅模式是什么?
想象一下你关注了一个公众号(比如”全栈老李”),每次我发新文章,你的微信就会收到通知。这就是发布订阅模式的现实例子——你不必主动来问我”有新文章了吗”,而是订阅后自动接收更新。
在前端开发中,发布订阅模式(也叫观察者模式)允许对象订阅其他对象的事件,并在事件发生时自动得到通知。这种松耦合的设计让代码更灵活、更易维护。
手写一个基础EventEmitter
让我们从零实现一个最简单的EventEmitter,理解其核心原理:
// 全栈老李的EventEmitter基础实现
class EventEmitter {
constructor() {
this.events = {
}; // 存储事件和对应的回调函数
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
return this; // 链式调用
}
// 发布事件
emit(eventName, ...args) {
const callbacks = this.events[eventName];
if (callbacks) {
callbacks.forEach(cb => cb.apply(this, args));
}
return this; // 链式调用
}
// 取消订阅
off(eventName, callback) {
const callbacks = this.events[eventName];
if (callbacks) {
this.events[eventName] = callbacks.filter(cb => cb !== callback);
}
return this;
}
// 一次性订阅
once(eventName, callback) {
const wrapper = (...args) => {
callback.apply(this, args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
return this;
}
}
这个实现虽然简单,但包含了发布订阅模式的核心功能。全栈老李提醒你注意几个关键点:
events对象用事件名作为key,存储回调函数数组
on方法添加订阅者
emit方法触发事件并执行所有回调
off方法取消订阅
once实现一次性订阅
真实场景中的使用示例
让我们看一个电商网站的实际案例:
// 购物车事件系统 - 全栈老李实战示例
class ShoppingCart {
constructor() {
this.emitter = new EventEmitter();
this.items = [];
}
addItem(item) {
this.items.push(item);
this.emitter.emit('itemAdded', item);
this.emitter.emit('cartUpdated', this.items);
}
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId);
this.emitter.emit('itemRemoved', itemId);
this.emitter.emit('cartUpdated', this.items);
}
}
// 使用示例
const cart = new ShoppingCart();
// 订阅商品添加事件
cart.emitter.on('itemAdded', item => {
console.log(`商品添加通知:${
item.name} 已加入购物车`);
// 这里可以更新UI通知、发送埋点等
});
// 订阅购物车更新事件
cart.emitter.on('cartUpdated', items => {
console.log('购物车更新了,当前商品数量:', items.length);
// 更新购物车图标数字、计算总价等
});
// 添加商品
cart.addItem({
id: 1, name: 'JavaScript高级程序设计', price: 99 });
在这个例子中,购物车状态的改变不需要直接调用UI更新方法,而是通过事件通知所有关心这些变化的组件。全栈老李认为这种解耦设计让代码更容易维护和扩展。
进阶:更复杂的事件系统
实际项目中,我们可能需要更强大的事件系统。让我们增强基础实现:
// 全栈老李的增强版EventEmitter
class AdvancedEventEmitter extends EventEmitter {
constructor() {
super();
this.maxListeners = 10; // 默认最大监听器数量
}
// 设置最大监听器数量
setMaxListeners(n) {
this.maxListeners = n;
return this;
}
// 获取事件监听器数量
listenerCount(eventName) {
const callbacks = this.events[eventName];
return callbacks ? callbacks.length : 0;
}
// 移除所有监听器
removeAllListeners(eventName) {
if (eventName) {
delete this.events[eventName];
} else {
this.events = {
};
}
return this;
}
// 异步触发事件
async emitAsync(eventName, ...args) {
const callbacks = this.events[eventName];
if (callbacks) {
await Promise.all(callbacks.map(cb => cb.apply(this, args)));
}
return this;
}
}
增强版增加了几个实用功能:
监听器数量限制
异步事件触发
更灵活的监听器管理
发布订阅在前端生态中的应用
发布订阅模式在前端无处不在,全栈老李给你举几个典型例子:
DOM事件系统:addEventListener就是最基础的发布订阅实现
Vue自定义事件:$on, $emit方法
Redux:store.subscribe监听状态变化
WebSocket:消息的订阅与发布
Node.js EventEmitter:几乎所有核心模块都继承自它
性能优化与注意事项
虽然发布订阅很强大,但全栈老李提醒你要注意几个问题:
内存泄漏:忘记取消订阅会导致回调函数无法被垃圾回收
调试困难:事件流不直观时,问题难以追踪
过度使用:简单场景直接用回调可能更直接
优化建议:
// 好习惯:在组件销毁时取消订阅
class MyComponent {
constructor() {
this.handleResize = () => {
/*...*/};
window.addEventListener('resize', this.handleResize);
}
destroy() {
window.removeEventListener('resize', this.handleResize);
}
}
课后作业:手写支持异步的事件总线
来挑战一个面试常见题:实现一个支持异步事件处理的事件总线系统,要求:
支持同步和异步事件发布
支持事件优先级
支持中间件机制
支持事件拦截
// 全栈老李的作业模板
class AsyncEventBus {
// 你的实现
}
// 测试用例
const bus = new AsyncEventBus();
bus.on('login', async (user) => {
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`1. 记录登录日志: ${
user.name}`);
}, {
priority: 1 });
bus.on('login', (user) => {
console.log(`2. 更新用户状态: ${
user.name}`);
}, {
priority: 2 });
(async () => {
await bus.emit('login', {
name: '全栈老李', id: 123 });
console.log('3. 登录流程结束');
})();
/*
期望输出顺序:
2. 更新用户状态: 全栈老李
1. 记录登录日志: 全栈老李
3. 登录流程结束
*/
把你的实现发在评论区,全栈老李会随机抽几位同学的代码进行点评!看看谁能写出最优雅的实现~
发布订阅模式是前端工程师必须掌握的核心设计模式,理解它不仅能写出更好的代码,还能更深入地理解前端生态中的各种库和框架。我是全栈老李,我们下期再见!
🔥 必看面试题
【3万字纯干货】前端学习路线全攻略!从小白到全栈工程师(2025版)
【初级】前端开发工程师面试100题(一)
【初级】前端开发工程师面试100题(二)
【初级】前端开发工程师的面试100题(速记版)
我是全栈老李,一个资深Coder!
写码不易,如果你觉得本文有收获,点赞 + 收藏走一波!感谢鼓励🌹🌹🌹






















暂无评论内容