为什么说发布 - 订阅是代码的 “万能胶水”?解耦逻辑全解析

前端场景题:揭秘发布-订阅模式,让你的代码不再"鸡同鸭讲"!

✨ 嘿,各位前端的"程序猿"们!在我们的日常开发中,是不是经常遇到这样的场景:一个模块需要知道另一个模块发生了什么,然后做出相应的反应?比如,用户点击了某个按钮,然后页面上的好几个地方都要跟着更新?如果你的代码里充斥着各种 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,那就相当于你彻底取关了这个"娱乐新闻速递"的某个频道,以后这个频道的所有新闻你都不会再收到了。

如果你传入了 typehandler,那就相当于你只取消了你自己的吃瓜反应,别人依然可以继续吃瓜。这里我们通过 findIndex 找到你的吃瓜反应在数组中的位置,然后用 splice 把它移除。如果这个事件的所有订阅者都取消了订阅,我们就会把这个事件从"八卦小本本"里彻底删除。

⚠️ 注意事项

  • 内存泄漏 :如果你订阅了事件,但是忘记取消订阅,那么即使你的组件被销毁了,它的处理函数依然会留在 EventCenter 里,这就会造成内存泄漏。所以,在组件销毁时,一定要记得调用 removeEventListener
  • 事件命名 :事件名要清晰明了,最好能一眼看出这个事件是干嘛的。比如 userLoggedInproductAddedToCart 等。

💡 总结与展望

发布-订阅模式就像一个高效的"信息中转站",它让你的代码模块之间解耦,提高了代码的灵活性、可维护性和可扩展性。在前端开发中,无论是组件通信、状态管理,还是日志记录、数据统计,发布-订阅模式都能大显身手。

学会了它,你的代码将不再"鸡同鸭讲",而是"心有灵犀一点通"!快去你的项目中试试吧!

相关推荐
小妖6661 分钟前
react-router 怎么设置 basepath 设置网站基础路径
前端·react.js·前端框架
xvmingjiang7 分钟前
Element Plus 中 el-input 限制为数值输入的方法
前端·javascript·vue.js
XboxYan24 分钟前
借助CSS实现自适应屏幕边缘的tooltip
前端·css
极客小俊25 分钟前
iconfont 阿里巴巴免费矢量图标库超级好用!
前端
小杨 想拼32 分钟前
使用js完成抽奖项目 效果和内容自定义,可以模仿游戏抽奖页面
前端·游戏
yvvvy35 分钟前
🐙 Git 从入门到面试能吹的那些事
前端·trae
狂炫一碗大米饭35 分钟前
事件委托的深层逻辑:当冒泡不够时⁉️
javascript·面试
张柏慈1 小时前
JavaScript性能优化30招
开发语言·javascript·性能优化
来自天蝎座的孙孙1 小时前
洛谷P1595讲解(加强版)+错排讲解
python·算法
GawynKing1 小时前
图论(5)最小生成树算法
算法·图论·最小生成树