发布订阅模式

100 阅读3分钟

定义

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在 同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在 发布订阅模式需要第三方来帮他们完成通信。

在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

举个例子: 中介充当第三个代理通信类鸡哥来订阅消息,就跟中介大哥说: 中介大哥,我会唱、跳、rap、篮球,哪天你需要用到人就来跟我说一声呀。中介大哥说:行行行,我知道了, 哪天需要你来唱跳rap就告诉你一声。老板来给中介发布消息说: 嘿,中介,我需要一个能会唱、跳、rap、篮球的人,你有没有,有的话就跟我说一下。

在这个过程中,老板不知道鸡哥的存在,鸡哥也不知道老板的存在, 他们之间的通信完全靠黑心中介来完成通话。

实现过程

发布订阅模式大概步骤如下:

  • 设计第三方信息代理通信类
  • 给代理通信方法添加$on方法, 表示鸡哥来订阅了消息
  • 给代理通信方法添加$emit方法,表示老板来发布了消息
  • 给代理通信方法添加$off方法,表示老板取消了消息

定义代理通信类

function PubSub() {
    // type 是事件类型, 形如 
    //   {
    //     '买房子': [ [Function ()], [Function ()] ],
    //     '买车子': [ [Function ()] ]
    //   }
    this.type = {}
}

定义订阅方法$on

/** 
 * 订阅消息
 * `$on` 向事件类型添加内容 
 * @param {*} evenName 事件名 (事件类型)
 * @param {*} callback 回调函数
 */
PubSub.prototype.$on = function (evenName, callback) {
    // 如果订阅的事件类型为空, 则创建一个事件类型数组
    if (!this.type[evenName]) {
        this.type[evenName] = []
    }
    this.type[evenName].push(callback)
}

定义发布方法$emit

/**
 * 发布消息
 * $emit 触发事件类型里的内容
 * @param {*} evenName 事件名 (事件类型)
 * @param {*} args 剩余参数,表示 发布的数据值为多少
 */
PubSub.prototype.$emit = function (evenName, ...args) {
    // 判断是否有订阅
    if (!this.type[evenName]) return;
    // 如果有订阅这个类型,通知订阅这个类型的人调用他们的回调函数 传递数据过去
    this.type[evenName].forEach(callback => {
        callback.call(this, args)
    });
}

定义删除事件类型方法

/**
 * $off 删除事件类型里的内容
 * 有两种情况:
 *  1. 删除整个事件类型, 比如删除 买房子类型
 *  2. 删除事件类型中的某个 回调
 * @param {*} type 事件名 (事件类型)
 * @param {*} callback 回调函数
 */
PubSub.prototype.$off = function (evenName, callback) {
    // 判断是否有订阅,即事件类型里是否有evenName这个类型的事件,没有的话就直接return
    if (!this.type[evenName]) return;
    // 如果没传callback , 表示删除整个事件类型
    if (!callback) {
        this.type[evenName] = null
    } else {
        // 如果有传, 表示删除这个回调函数
        this.type[evenName] = this.type[evenName].filter(item => item !== callback)
    }
}

完整代码

function PubSub() {
    // type 是事件类型, 形如 
    //   {
    //     '买房子': [ [Function ()], [Function ()] ],
    //     '买车子': [ [Function ()] ]
    //   }
    this.type = {}
}

/** 
 * 订阅消息
 * `$on` 向事件类型添加内容 
 * @param {*} evenName 事件名 (事件类型)
 * @param {*} callback 回调函数
 */
PubSub.prototype.$on = function (evenName, callback) {
    // 如果订阅的事件类型为空, 则创建一个事件类型数组
    if (!this.type[evenName]) {
        this.type[evenName] = []
    }
    this.type[evenName].push(callback)
}

/**
 * 发布消息
 * $emit 触发事件类型里的内容
 * @param {*} evenName 事件名 (事件类型)
 * @param {*} args 剩余参数,表示 发布的数据值为多少
 */
PubSub.prototype.$emit = function (evenName, ...args) {
    // 判断是否有订阅
    if (!this.type[evenName]) return;
    // 如果有订阅这个类型,通知订阅这个类型的人调用他们的回调函数 传递数据过去
    this.type[evenName].forEach(callback => {
        callback.call(this, args)
    });
}

/**
 * $off 删除事件类型里的内容
 * 有两种情况:
 *  1. 删除整个事件类型, 比如删除 买房子类型
 *  2. 删除事件类型中的某个 回调
 * @param {*} type 事件名 (事件类型)
 * @param {*} callback 回调函数
 */
PubSub.prototype.$off = function (evenName, callback) {
    // 判断是否有订阅,即事件类型里是否有evenName这个类型的事件,没有的话就直接return
    if (!this.type[evenName]) return;
    // 如果没传callback , 表示删除整个事件类型
    if (!callback) {
        this.type[evenName] = null
    } else {
        // 如果有传, 表示删除这个回调函数
        this.type[evenName] = this.type[evenName].filter(item => item !== callback)
    }
}

const pubsub = new PubSub()
pubsub.$on('唱', function (value) {
    console.log('鸡哥订阅了唱歌,老板给了:', value);
})
pubsub.$on('跳', function (value) {
    console.log('鸡哥订阅了跳舞,老板给了:', value);
})
pubsub.$on('rap', function (value) {
    console.log('鸡哥订阅了rap,老板给了:', value);
})
pubsub.$on('篮球', function (value) {
    console.log('鸡哥订阅了篮球,老板给了:', value);
})
pubsub.$on('唱', function (value) {
    console.log('张三订阅了唱歌,老板给了:', value);
})
pubsub.$on('篮球', function (value) {
    console.log('老六订阅了篮球,老板给了:', value);
})

// 老板来发布任务了
pubsub.$emit('唱', '5毛球')
pubsub.$emit('篮球', "10块钱")
console.log(pubsub, 'pubsub1');

// 唱的太难题了,别唱歌了
pubsub.$off('唱')
console.log('----------------------------------------------------------');
console.log(pubsub, 'pubsub2');

运行结果

image.png