通俗讲解 发布-订阅模式

简介

发布-订阅模式是一种消息传递模式,它允许发送者(发布者)发送消息而不关心谁将接收这些消息,同样地,接收者(订阅者)可以监听他们感兴趣的消息,而不需要知道这些消息来自哪里。这种模式在JavaScript中常用于实现事件处理系统,如DOM事件监听或自定义事件系统。

特点

  • 解耦:发布者和订阅者之间不直接依赖,它们之间通过事件中心(Event Center)进行通信,实现了解耦。

  • 一对多:一个事件可以有多个订阅者,当事件被发布时,所有订阅了该事件的订阅者都会收到通知。

  • 异步通信:发布者和订阅者之间的通信是异步的,发布者发布事件时不会等待订阅者的处理结果。

  • 扩展性好:新的订阅者可以随时订阅事件,旧的订阅者也可以随时取消订阅,无需修改发布者代码。

流程

  • 定义事件总线:首先,需要定义一个事件中心对象,该对象用于管理事件和订阅者。

  • 订阅事件:订阅者通过调用事件中心的订阅方法,指定要订阅的事件类型和回调函数。

  • 发布事件:发布者通过调用事件中心的发布方法,并传递事件类型和相关数据来触发事件。

  • 触发回调:事件中心接收到发布请求后,会遍历该事件类型下的所有订阅者,并依次调用它们的回调函数,传递事件数据。


通俗举例

以下将使用消费者邮局订阅报纸邮递员 来通俗举例解释发布-订阅模式的关系:

角色对应

  • 发布者:报纸出版社(负责发布报纸内容)

  • 事件:报纸内容(如新闻、广告等)

  • 事件总线:邮局(负责传递报纸)

  • 订阅者:消费者(订阅报纸的人)

  • 邮递员:邮局的工作人员,负责将报纸从邮局传递到消费者手中

运行流程

1. 订阅(Subscribe)

  • 消费者(订阅者)到邮局(事件总线)填写订阅单,指定他们感兴趣的报纸(事件类型)。

  • 邮局(事件总线)记录消费者的订阅信息,包括消费者的地址和所订阅的报纸类型。

2. 发布(Publish)

  • 报纸出版社(发布者)完成当天的报纸编辑,将报纸内容(事件)发送到邮局(事件总线)。

  • 邮局(事件总线)接收到报纸后,会检查哪些消费者订阅了这份报纸。

3. 通知(Notify)

  • 邮局(事件总线)将报纸(事件)交给邮递员(传递者)。

  • 邮递员根据消费者的订阅信息,将报纸投递到消费者的地址(即通知订阅者)。

关键点

  • 松耦合:消费者(订阅者)和报纸出版社(发布者)之间不直接通信,而是通过邮局(事件总线)进行间接通信。

  • 事件类型:消费者(订阅者)可以订阅不同类型的报纸(如日报、晚报、财经报等),这对应于不同的事件类型。

  • 异步性:消费者(订阅者)可以在报纸发布之前或之后订阅,邮局(事件总线)会负责将后续的报纸投递给已经订阅的消费者。

代码实现

// 创建一个事件中心(模拟邮局)  
class EventCenter {
    constructor() {
        this.subscribers = {}; // 订阅者列表,按事件类型存储  
    }

    // 订阅事件(消费者订阅报纸)  
    subscribe (eventType, callback) {
        if (!this.subscribers[eventType]) {
            this.subscribers[eventType] = [];
        }
        this.subscribers[eventType].push(callback);
    }

	// 取消订阅事件(消费者取消订阅报纸)  
	unsubscribe(eventType, callback) {
	    if (this.subscribers[eventType]) {
	        const index = this.subscribers[eventType].indexOf(callback);
	        if (index !== -1) {
	            this.subscribers[eventType].splice(index, 1);
	        }
	    }
	}  

    // 发布事件(报纸出版社发布报纸)  
    publish (eventType, data) {
        // 如果有订阅者订阅了这个事件  
        if (this.subscribers[eventType]) {
            // 遍历所有订阅者,并通知他们(传递报纸)  
            this.subscribers[eventType].forEach(callback => {
                callback(data);
            });
        }
    }
    
    // 清除所有订阅者  
    clearAllSubscriptions () {
        this.subscribers = {};
    }
}
// 创建一个事件中心实例  
const eventCenter = new EventCenter();

// 消费者(订阅者)  
function consumerA (newspaper) {
    console.log('消费者A收到了报纸:', newspaper);
}

function consumerB (newspaper) {
    console.log('消费者B收到了报纸:', newspaper);
}

// 消费者A订阅日报  
eventCenter.subscribe('日报', consumerA);
// 消费者B也订阅日报  
eventCenter.subscribe('日报', consumerB);

// 报纸出版社(发布者)发布日报  
eventCenter.publish('日报', '今天的头条新闻...');

// 如果消费者C稍后订阅,他将不会收到之前发布的日报  
function consumerC (newspaper) {
    console.log('消费者C收到了报纸:', newspaper);
}

// 消费者C订阅日报(但日报已经发布过了,所以消费者C不会收到这次的报纸)  
eventCenter.subscribe('日报', consumerC);

// 如果报纸出版社再次发布日报,消费者C将会收到  
eventCenter.publish('日报', '明天的头条预告...');


// 假设消费者A后来决定不再订阅日报  
eventCenter.unsubscribe('日报', consumerA);  
  
// 报纸出版社再次发布日报,消费者A将不会收到,但消费者B和消费者C将会收到  
eventCenter.publish('日报', '后天的头条预告...');

// 清除所有订阅者  
eventCenter.clearAllSubscriptions();

// 尝试发布事件,但此时没有订阅者  
eventCenter.publish('日报', '没有订阅者将收到此消息');

Vue 中的发布-订阅模式

简单地说,发布者-订阅者模式的流程就是:监听器监听数据状态变化, 一旦数据发生变化,则会通知对应的订阅者,让订阅者执行对应的业务逻辑 。

首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图

  1. 实现一个监听器 Observer ,用来劫持并监听所有属性,如果属性发生变化,就通知订阅者;
  2. 实现一个订阅器 Dep,用来收集订阅者,对监听器 Observer 和 订阅者 Watcher 进行统一管理;
  3. 实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的方法,从而更新视图;
  4. 实现一个解析器 Compile,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化。

在这里插入图片描述

  1. 监听器(Observer)

    • 当Vue实例创建时,Vue会遍历data对象中的所有属性。

    • 使用Object.defineProperty()将这些属性转化为getter/setter,从而能够拦截对这些属性的访问和修改。

    • 当数据属性被访问时,getter会被调用,Vue会开始收集依赖,并为每个依赖创建一个订阅者Watcher对象。每个Watcher在实例化时,会将自己添加到它所依赖的属性的依赖列表中(由订阅器Dep管理)

  2. 订阅器(Dep)

    • Dep是Vue内部的一个类,用于管理每个数据属性的依赖列表。

    • 当数据属性被访问时,相关的Watcher会被添加到Dep的依赖列表中。

    • 当数据属性发生变化时,setter会被调用, Dep会通知所有依赖于该属性的Watcher进行更新。

  3. 订阅者(Watcher)

    • Watcher是Vue中的观察者对象,它负责监听数据属性的变化,并在数据变化时触发相应的更新操作。

    • Watcher通常与组件的渲染过程相关联,当组件需要渲染时,会创建一个Watcher来监听相关的数据属性。

    • 当数据属性发生变化时,Dep会通知所有依赖于该属性的Watcher进行更新。每个Watcher接收到通知后,会调用其update方法,它会重新计算虚拟DOM,并与之前的虚拟DOM进行比较(diffing算法),以找出需要更新的实际DOM节点。

  4. 解析器(Compile)

    • 解析器的作用是将模板解析成抽象语法树(AST)。

    • AST是源代码语法结构的一种抽象表示,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

    • 在Vue中,解析器会扫描和解析模板中的每个节点,找到其中的指令(如v-model、v-on等)和表达式,并将它们与对应的数据属性进行绑定。

    • 当数据属性发生变化时,由于已经通过getter/setter进行了拦截和依赖收集,所以相关的Watcher会被触发,进而触发组件的重新渲染。


注意事项

  1. 内存管理:确保在不再需要某个订阅者时取消订阅,以避免内存泄漏。

  2. 事件命名:为事件类型选择具有描述性和唯一性的名称,以避免命名冲突。

  3. 错误处理:在回调函数和事件中心的方法中添加适当的错误处理逻辑,以确保程序的健壮性。

  4. 避免无限循环:确保在回调函数中不会触发相同的事件,这可能导致无限循环。

  5. 性能考虑:当订阅者数量较多或事件发布频率较高时,需要考虑性能优化,如使用WeakMap来存储订阅者等。

  6. 兼容性:在浏览器中实现时,要注意不同浏览器对事件处理的支持情况,确保代码具有良好的兼容性。

  • 28
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫老板的豆

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值