前端场景题:揭秘发布-订阅模式,让你的代码不再"鸡同鸭讲"!
✨ 嘿,各位前端的"程序猿"们!在我们的日常开发中,是不是经常遇到这样的场景:一个模块需要知道另一个模块发生了什么,然后做出相应的反应?比如,用户点击了某个按钮,然后页面上的好几个地方都要跟着更新?如果你的代码里充斥着各种 if/else
和直接调用,那恭喜你,你的代码可能正在上演一出"鸡同鸭讲"的戏码!
别担心,今天我们要介绍一个"万能胶水"------发布-订阅模式,它能让你的模块之间"心有灵犀一点通",彻底告别"鸡同鸭讲"的尴尬!
🔄 发布-订阅模式是个啥?
想象一下,你是个八卦小能手,想知道娱乐圈的最新动态。你不需要每天打电话给每个明星问他们今天干了啥,对吧?你只需要订阅一个"娱乐新闻速递"公众号,一旦有大瓜,公众号就会立刻通知你!
发布-订阅模式就是这么个意思:
- 发布者 (Publisher):就像那些爆料的狗仔队,他们只负责"发布"新闻,不关心谁会看。在代码里,就是触发事件的那个模块。
- 订阅者 (Subscriber):就像我们这些吃瓜群众,只负责"订阅"自己感兴趣的新闻,一旦有新闻就"接收"并"消化"。在代码里,就是监听事件并执行相应操作的模块。
- 调度中心 (Event Center):这才是真正的"幕后大佬",它负责管理所有的"订阅"关系,当"发布者"发布新闻时,它会把新闻精准地推送给所有"订阅者"。它就像那个"娱乐新闻速递"公众号。
核心思想:发布者和订阅者之间没有直接的联系,它们都只和调度中心打交道。这样一来,模块之间的耦合度就大大降低了,代码也变得更加灵活和可维护!
🔧 亲手打造你的"娱乐新闻速递"------EventCenter
接下来,我们就用代码来模拟一个简单的"娱乐新闻速递"调度中心,也就是我们图片中看到的 EventCenter
类。别看它名字高大上,实现起来可一点都不复杂!
javascript
class EventCenter{
// 1. 定义事件容器,用来装事件数组
let handlers = {}
// 2. 添加事件方法, 参数:事件名 事件方法
addEventListener(type, handler) {
// 创建新数组容器
if (!this.handlers[type]) {
this.handlers[type] = []
}
// 存入事件
this.handlers[type].push(handler)
}
// 3. 触发事件, 参数:事件名 事件参数
dispatchEvent(type, params) {
// 若没有注册该事件则抛出错误
if (!this.handlers[type]) {
return new Error('该事件未注册')
}
// 触发事件
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 4. 事件移除, 参数:事件名 要删除事件, 若无第二个参数则删除该事件的订阅和发布
removeEventListener(type, handler) {
if (!this.handlers[type]) {
return new Error('事件无效')
}
if (!handler) {
// 移除事件
delete this.handlers[type]
} else {
const index = this.handlers[type].findIndex(el => el === handler)
if (index === -1) {
return new Error('无该绑定事件')
}
// 移除事件
this.handlers[type].splice(index, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
1. 📦 事件容器 handlers
javascript
let handlers = {}
这个 handlers
对象就是我们的"八卦小本本",它用来存储所有事件和对应的处理函数。key
是事件名(比如"明星出轨"、"新剧开播"),value
是一个数组,里面装着所有订阅了这个事件的处理函数(也就是那些等着吃瓜的订阅者)。
2. ➕ 订阅事件 addEventListener
javascript
addEventListener(type, handler) {
if (!this.handlers[type]) {
this.handlers[type] = []
}
this.handlers[type].push(handler)
}
这个方法就是让吃瓜群众"订阅"新闻的入口。当你调用 eventCenter.addEventListener('明星出轨', () => console.log('我惊呆了!'))
时,就相当于你订阅了"明星出轨"这个事件,并且告诉调度中心,一旦有这个事件,就执行你的吃瓜反应。
如果这个事件是第一次被订阅,我们就会给它创建一个新的数组来存放处理函数。然后,就把你的吃瓜反应(handler
)添加到这个数组里。
3. 🔥 发布事件 dispatchEvent
javascript
dispatchEvent(type, params) {
if (!this.handlers[type]) {
return new Error('该事件未注册')
}
this.handlers[type].forEach(handler => {
handler(...params)
})
}
当狗仔队爆出大瓜时,他们就会调用 eventCenter.dispatchEvent('明星出轨', ['某某明星', '某某导演'])
。调度中心收到消息后,会先检查有没有人订阅这个事件。如果没有,那这个瓜就白爆了(抛出错误)。
如果有订阅者,调度中心就会遍历所有订阅了这个事件的处理函数,然后挨个执行它们,并且把"瓜料"(params
)传递给它们。于是,所有吃瓜群众就都收到了消息,开始各自的表演!
4. ➖ 取消订阅 removeEventListener
javascript
removeEventListener(type, handler) {
if (!this.handlers[type]) {
return new Error('事件无效')
}
if (!handler) {
delete this.handlers[type]
} else {
const index = this.handlers[type].findIndex(el => el === handler)
if (index === -1) {
return new Error('无该绑定事件')
}
this.handlers[type].splice(index, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
有时候,你可能吃瓜吃腻了,或者对某个明星的八卦不感兴趣了,这时候就可以取消订阅。removeEventListener
方法就是干这个的。
如果你只传入事件名 type
,不传入 handler
,那就相当于你彻底取关了这个"娱乐新闻速递"的某个频道,以后这个频道的所有新闻你都不会再收到了。
如果你传入了 type
和 handler
,那就相当于你只取消了你自己的吃瓜反应,别人依然可以继续吃瓜。这里我们通过 findIndex
找到你的吃瓜反应在数组中的位置,然后用 splice
把它移除。如果这个事件的所有订阅者都取消了订阅,我们就会把这个事件从"八卦小本本"里彻底删除。
⚠️ 注意事项
- 内存泄漏 :如果你订阅了事件,但是忘记取消订阅,那么即使你的组件被销毁了,它的处理函数依然会留在
EventCenter
里,这就会造成内存泄漏。所以,在组件销毁时,一定要记得调用removeEventListener
! - 事件命名 :事件名要清晰明了,最好能一眼看出这个事件是干嘛的。比如
userLoggedIn
、productAddedToCart
等。
💡 总结与展望
发布-订阅模式就像一个高效的"信息中转站",它让你的代码模块之间解耦,提高了代码的灵活性、可维护性和可扩展性。在前端开发中,无论是组件通信、状态管理,还是日志记录、数据统计,发布-订阅模式都能大显身手。
学会了它,你的代码将不再"鸡同鸭讲",而是"心有灵犀一点通"!快去你的项目中试试吧!