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

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

想象这样一个场景:康总(一个钱多但不太聪明的土豪)冲进房产中介,把一沓钞票拍在桌上:"老子要买房!"虽然现在没房源,但精明的中介怎么会放过这种狗大户?他们立即把康总拉进了"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的响应式系统正是基于此原理。

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

相关推荐
胡gh5 小时前
页面卡成PPT?重排重绘惹的祸!依旧性能优化
前端·javascript·面试
胡gh5 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
言兴5 小时前
# 深度解析 ECharts:从零到一构建企业级数据可视化看板
前端·javascript·面试
山有木兮木有枝_6 小时前
TailWind CSS
前端·css·postcss
烛阴6 小时前
TypeScript 的“读心术”:让类型在代码中“流动”起来
前端·javascript·typescript
杨荧6 小时前
基于Python的农作物病虫害防治网站 Python+Django+Vue.js
大数据·前端·vue.js·爬虫·python
Moment7 小时前
毕业一年了,分享一下我的四个开源项目!😊😊😊
前端·后端·开源
程序视点8 小时前
Escrcpy 3.0投屏控制软件使用教程:无线/有线连接+虚拟显示功能详解
前端·后端
silent_missile8 小时前
element-plus穿梭框transfer的调整
前端·javascript·vue.js
专注VB编程开发20年8 小时前
OpenXml、NPOI、EPPlus、Spire.Office组件对EXCEL ole对象附件的支持
前端·.net·excel·spire.office·npoi·openxml·spire.excel