从买房到代码:发布订阅模式的"房产中介"之旅

一、初识发布订阅:房产中介的营销套路

想象这样一个场景:康总(一个钱多但不太聪明的土豪)冲进房产中介,把一沓钞票拍在桌上:"老子要买房!"虽然现在没房源,但精明的中介怎么会放过这种狗大户?他们立即把康总拉进了"VIP购房群",等有新房源时就在群里通知。这就是发布订阅模式的现实版:

  • 订阅 :康总加群(on方法)
  • 发布 :中介发房源通知(emit方法)
  • 事件中心 :那个微信群(eventList对象)
javascript 复制代码
class EventEmitter {
  constructor() {
    this.eventList = {}; // 中介的客户登记簿
  }

  // 客户登记(订阅)
  on(eventName, cb) {
    if (!this.eventList[eventName]) {
      this.eventList[eventName] = []; // 新建一个客户群
    }
    this.eventList[eventName].push(cb); // 把客户加进群
  }

  // 发布通知(emit)
  emit(eventName) {
    const handlers = this.eventList[eventName]?.slice(); // 安全拷贝
    handlers?.forEach(handler => handler()); // 在群里@所有人
  }
}

// 康总的购房故事
const _event = new EventEmitter();

function kang() {
  console.log('我是康总,快给我房子!');
}

_event.on('hasHouse', kang);  // 康总加群
_event.emit('hasHouse');      // 中介发通知 → 输出:我是康总,快给我房子!

二、基础实现的三要素

1. 事件存储中心

this.eventList = {} 就像中介的客户登记表:

javascript 复制代码
{
  'hasHouse': [kang, ji],  // 想买房的人
  'hasCar': [hao]          // 想买车位的人
}

2. 订阅方法(on)

  • 检查是否有对应"客户群"
  • 没有就新建群聊
  • 把回调函数加入群组

3. 发布方法(emit)

  • 安全拷贝回调数组(避免执行过程中数组被修改)
  • 遍历执行所有订阅者

三、让模式更健壮:取消订阅功能

现实中的康总可能会被其他中介挖走,这时候就需要off方法:

javascript 复制代码
class EventEmitter {
  // ...原有代码
  
  // 客户退群(取消订阅)
  off(eventName, cb) {
    const callbacks = this.eventList[eventName];
    if (!callbacks) return;
    
    const index = callbacks.indexOf(cb);
    if (index !== -1) {
      callbacks.splice(index, 1); // 从群里踢人
    }
  }
}

// 康总生气了要退群
_event.off('hasHouse', kang);
_event.emit('hasHouse'); // 此时不会再通知康总

面试注意点

  • 使用indexOf查找回调函数
  • 检查index !== -1才执行删除
  • 直接修改原数组(无需返回新数组)

四、高级特性:一次性订阅

有些客户就像渣男,只想听一次房源通知(比如特价房):

javascript 复制代码
class EventEmitter {
  // ...原有代码
  
  once(eventName, cb) {
    const onceWrapper = () => {
      cb(); // 执行原回调
      this.off(eventName, onceWrapper); // 自动退群
    };
    this.on(eventName, onceWrapper); // 先加群
  }
}

// 康总只想听一次特价房通知
_event.once('discountHouse', () => {
  console.log('康总:这套特价房我要了!');
});

_event.emit('discountHouse'); // 会触发
_event.emit('discountHouse'); // 不再触发

实现技巧

  1. 包装原回调函数
  2. 在包装函数内执行off自我移除
  3. 使用闭包保持对原回调的引用

五、发布订阅的"亲兄弟":观察者模式

提到发布订阅模式,就不得不介绍它的"亲兄弟"------观察者模式。这对兄弟虽然相似,但性格迥异:

  • 发布订阅像个社交达人,通过中间人(事件中心)牵线搭桥
  • 观察者则像个控制狂,直接盯着目标对象的一举一动

让我们从一个简单的计数器例子开始,展示观察者模式如何实现数据和视图的自动同步:

JavaScript 复制代码
<!DOCTYPE html>
<html>
<head>
  <title>计数器观察者</title>
</head>
<body>
  <h2 id="counter">0</h2>
  <button id="increment">+1</button>

  <script>
    // 被观察的数据对象
    const counterData = {
      value: 0
    };

    // 中间变量防止递归
    let _value = counterData.value;

    // 观察者函数 - 更新DOM
    function updateView(newValue) {
      document.getElementById('counter').textContent = newValue;
    }

    // 使用defineProperty劫持数据
    Object.defineProperty(counterData, 'value', {
      get() {
        return _value;
      },
      set(newValue) {
        _value = newValue;
        updateView(newValue); // 数据变化时自动更新视图
      }
    });

    // 按钮点击事件
    document.getElementById('increment').addEventListener('click', () => {
      counterData.value += 1; // 修改数据会自动更新视图
    });
  </script>
</body>
</html>

六、观察者模式的核心原理

1.观察者模式的"甜蜜约束":数据劫持

老骥有了对象后,他的属性访问就变得不再"自由"了。让我们看看这个有趣的约束机制:

javascript 复制代码
const 老骥 = {
  a: 1 // 初始值
};

let _a = 老骥.a; // 中间变量

Object.defineProperty(老骥, 'a', {
  get() {
    console.log('【女友查岗】你为什么要访问这个属性?');
    return _a; // 返回实际值
  },
  set(newVal) {
    console.log(`【女友审批】想从 ${_a} 改成 ${newVal}?`);
    if(newVal > 100) {
      console.log('【女友驳回】数值太大,不允许!');
      return;
    }
    _a = newVal; // 通过审批
    console.log('【女友批准】修改成功~');
  }
});

// 测试访问
console.log(老骥.a); // 触发getter
老骥.a = 50;        // 正常修改
老骥.a = 200;       // 被驳回

核心特点解析:

  1. 访问拦截

    • 每次读取属性都会经过get方法
    • 可以在这里添加访问日志、权限检查等
  2. 修改控制

    • 所有赋值操作都会触发set方法
    • 可以在setter中添加业务规则验证
  3. 数据保护

    • 通过中间变量_a存储实际值
    • 外部无法直接修改真实数据

2. 观察者三要素

  1. Subject(主题) :被观察的数据对象
  2. Observer(观察者) :响应数据变化的函数
  3. Dependency(依赖) :建立数据与观察者的关系

3、与发布订阅模式的关键区别

特性 观察者模式 发布订阅模式
耦合度 主题直接维护观察者列表 通过事件中心完全解耦
通信方式 直接调用观察者方法 通过事件名称匹配
典型应用 Vue响应式系统 Node.js EventEmitter

七、设计模式总结:发布订阅与观察者模式

发布订阅模式和观察者模式都是实现对象间通信的重要设计模式。发布订阅模式通过事件中心解耦发布者和订阅者,就像房产中介连接买房者和卖房者,典型实现如Node.js的EventEmitter。观察者模式则更直接,被观察者维护观察者列表并在状态变化时主动通知,如同"控制狂"般直接监控对象变化,Vue的响应式系统正是基于此原理。

关键区别在于:发布订阅模式通过中介实现多对多通信,松耦合但稍复杂;观察者模式直接维护依赖关系,简单直接但耦合度较高。两种模式各有所长,发布订阅适合跨组件通信,观察者适合数据响应式场景。理解它们的实现原理和适用场景,是掌握现代前端框架的关键。

相关推荐
张拭心26 分钟前
拭心 7 月日复盘|个体在 AI 时代的挑战
前端
这是个栗子36 分钟前
express-jwt报错:Error: algorithms should be set
前端·npm·node.js
Dolphin_海豚39 分钟前
vapor 的 IR 是如何被 generate 到 render 函数的
前端·vue.js·vapor
小妖66643 分钟前
Next.js 怎么使用 Chakra UI
前端·javascript·ui
胡西风_foxww1 小时前
从数据丢失到动画流畅:React状态同步与远程数据加载全解析
前端·javascript·react.js·同步·异步·数据·状态
格调UI成品1 小时前
[特殊字符] 数据可视化结合 three.js:让 3D 呈现更精准,3 个优化经验谈
javascript·3d·信息可视化
初遇你时动了情2 小时前
JS中defineProperty/Proxy 数据劫持 vue3/vue2双向绑定实现原理,react 实现原理
javascript·vue.js·react.js
阿华的代码王国2 小时前
【Android】RecyclerView实现新闻列表布局(1)适配器使用相关问题
android·xml·java·前端·后端
汪子熙2 小时前
Angular 最新的 Signals 特性详解
前端·javascript
Spider_Man2 小时前
前端路由双雄传:Hash vs. History
前端·javascript·html