JavaScript中的发布/订阅模式

观察者模式

观察者模式中,多个观察者监听同一个目标对象。当目标对象的状态发生改变的时候,通知所有观察者,进行更新操作。 它们之间是直接进行通信的,由发布者直接通知观察者进行更新。

发布/订阅模式

消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。
而是将发布的消息分为不同的类别(消息过滤),无需了解哪些订阅者(如果有的话)可能存在。
同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
——维基百科

发布/订阅模式观察者模式最大的区别就是,发布者订阅者并不会直接进行通信,而是交给一个消息中间件——消息代理(message broker)。
订阅者只需要告诉消息代理,它需要接受哪些信息。发布者只需要告诉消息代理,它发送了什么信息,然后消息代理将消息过滤,发送给订阅者。双方并不关心对方是否存在。

消息过滤

那么消息代理是怎么进行过滤的呢?其实有两种方式:

  • 基于主题进行过滤
  • 基于内容进行过滤
基于主题

发布者负责对消息进行分类,发布到主题或信息通道上。订阅了同一主题的所有订阅者,都将接收到主题里的所有内容。

基于内容

订阅者负责对消息进行分类,只有消息的属性或内容满足订阅者定义的条件时,才会投递到该订阅者

有些系统中支持两种混合:发布者发布内容到主题,订阅者基于内容的订阅注册到一个或多个主题。

两者对比

  • 观察者模式里是松耦合的,而在发布/订阅模式里是解耦的。
  • 观察者模式里大多是同步操作,而发布/订阅模式大多是异步操作。
  • 观察者模式里Observer是知道Publisher的,Publisher也一直保持对Observer的记录。而发布/订阅模式里,它们并不知道对方的存在。并且只通过消息代理进行通信。
  • 观察者模式大多是存在应用程序内部,而发布/订阅模式存在于两个应用程序之间。

例子——Event Bus

Vue中如果在不使用Vuex的情况下,实现两个组件通信,可以使用Event Bus。它的作用就是作为一个全局事件总线,来实现两个相隔千里的组件相互通信。其实现的原理就是使用发布/订阅模式。让全局事件总线来充当消息代理

实现一个Event Bus

EventBus.js

// 本质上EventBus就是一个Vue实例
const bus = new Vue();
export default bus;

main.js

import bus from 'EventBus所在的路径';
// 注册到Vue原型上
Vue.prototype.bus = bus;

AComponent.js

// 监听"sayHi"事件,第二参数是监听到该事件时的执行回调函数
this.bus.$on('sayHi', callback);

BComponent.js

// 派发"sayHi"事件,第二个参数是要传进"回调函数"的参数
this.bus.$emit('sayHi', params);

来实现一个EventBus

class EventBus {
    constructor() {
        // 使用Map来存储每一个EventName对应的所有回调函数
        this.handlers = new Map();
    }
    // 监听目标事件,接收事件名和回调函数
    on(eventName, callback) {
        const { handlers } = this;
        // 如果EventName对应的回调函数栈存在,则继续往里面存新的回调函数
        if (handlers.get(EventName)) {
            handlers.get(EventName).push(callback);
        } else {
            // 否则创建新的回调函数栈
            handlers.set(EventName, [callback]);
        }
    }
    // 触发目标事件,传入事件名和参数
    emit(eventName, ...args) {
        const { handlers } = this;
        // 先查询该方法名是否存在
        if (handlers.get(eventName)) {
            // 存在则调用所有订阅者的回调函数
            handlers.get(eventName).forEach(callback => callback(...args));
        } else {
            // 不存在就抛出异常
            throw new Reference('Methods not exits');
        }
        
    }
    // 移除某个事件里的指定回调函数
    off(eventName, callback) {
        const { handlers } = this;
        const index = handlers.get(eventName).indexOf(callback);
        if (index > -1) {
            handlers.get(eventName).splice(index, 1);
        } else {
            throw new Reference('Methods not exits');
        }
    }
    // 注册单次的监听器
    once(eventName, callback) {
        const wrapper = (...args) => {
            callback(..args);
            this.off(eventName, callback);
        }
        this.on(eventName, wrapper);
    }
}

参考

https://zh.wikipedia.org/wiki/%E5%8F%91%E5%B8%83/%E8%AE%A2%E9%98%85
https://zh.wikipedia.org/wiki/%E6%B6%88%E6%81%AF%E4%BB%A3%E7%90%86

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值