手写发布订阅模式笔记自用

75 阅读3分钟

发布订阅模式主要分为两个部分: 订阅者发布者

订阅者—— 订阅者期望收到发布者发布的消息并且是发布时立即收到

订阅者需要给发布者传递订阅的事件名以及该事件发布后进行的回调函数

发布者—— 发布者在发布时立即告诉所有订阅者

发布者需要存储所有事件以及这些事件对应的回调函数,当事件发布时,执行订阅者存储的回调函数(也就是通知订阅者)

实现思路

  1. 定义一个subscribes对象,key为订阅的事件名,value为一个数组subscribeCallbacks(保存该事件对应的所有回调函数)
  2. 定义on方法,接收订阅的事件名和事件发布时调用的回调函数,调用on方法将回调函数push进该事件对应的subscribeCallbacks内
  3. 定义emit方法,接收发布的事件以及传递的参数。调用emit方法执行该事件的subscribeCallbacks数组内的所有回调函数(通知订阅者)并传递参数。
class PublishSubscribeBasic {
    subscribes = {} //保存所有的订阅事件

    on(subscribe, callback) {
       const subscribeCallbacks = this.subscribes[subscribe] || []
       this.subscribes[subscribe] = [callback, ...subscribeCallbacks] 
    }
    // 发布函数
    emit(subscribe, ...args) {
        const subscribeCallbacks = this.subscribes[subscribe] || []
        subscribeCallbacks.forEach(callback => {
            callback.call(this,args)            
        });
    }
}

const pubsub = new PublishSubscribeBasic();
pubsub.on('A', (args) => console.log(args));	// 订阅一个 A 事件并传递回调函数
setTimeout(() => pubsub.emit('A', '这是 A 收到的第一个参数'), 1000)

改造

上面的版本有几个缺点:仅支持订阅事件,但不支持取消订阅,基础版采用的是 Object 保存所有订阅事件以及 Array 来保存事件对应的回调函数,因为发布订阅一般采用单例模式,即在全局中一般仅用一个对象来实现发布订阅所有的事件,所以一般会使用这个对象来 存储 较多的数据、插入和 查找 较多的次数,所以可以使用 Map 来替换 Object。

如何取消订阅

取消订阅就是在subscribeCallbacks这个map中(不用数组)删除该订阅者的回调函数,那么我们必须需要一个回调函数的唯一标识,我们这里用Date.now()+Math.random()来表示这一唯一标识,找到这个唯一标识(此为map的key),删除该键值对即可

实现步骤

  1. 定义Map subscribes,key为事件名,value为事件对应的所有回调函数(用map subscribeCallbacks保存),用来保存各个事件对应的回调函数。
  2. 定义on方法,接收事件名以及回调函数,调用on方法会将key:唯一标识(uuId),value:回调函数存入该事件对应的map subscribeCalbacks中。同时on方法会将唯一标识uuid返回给订阅者(取消订阅时传入)
  3. 定义emit方法,接收事件名及参数,依旧是遍历调用事件对应的所有回调函数,传递参数。
  4. 定义remove方法,接收一个标识,如果标识为事件名,则删除key=事件名的键值对,如果标识为某个回调函数的uuId,则删除该key=uuId的键值对,如果查找不到,则返回错误。
class PublishSubscribeBasic {
    subscribes = new Map()
    getUUID() {
        return String(Date.now()) + String(Math.random());
    }
    getSubscribe(subscribe) {
        return this.subscribes.get(subscribe)
    }
    on(subscribe, callback) {
        const uuid = this.getUUID()
        const subscribeCallbacks = this.subscribes.get(subscribe) || new Map()
        subscribeCallbacks.set(uuid, callback)
        this.subscribes.set(subscribe, subscribeCallbacks)
        return uuid
    }
    emit(subscribe, ...args) {
        const subscribeCallbacks = this.subscribes.get(subscribe) || new Map()
        for(const [uuid, callback] of subscribeCallbacks) {
            callback.call(this, ...args)
        }
    }
    remove(value) {
        const isSubscribe = this.subscribes.get(value) //是否是事件名
        if(isSubscribe) {
            this.subscribes.delete(value)
        } else {
            for(const [subscribe, subscribeCallbacks] of subscribes) {
                if(subscribeCallbacks.has(value)) {
                    subscribeCallbacks.delete(value)
                }
            }
        }

    }
}

const pubsub = new PublishSubscribeBasic();
pubsub.on('A', (args) => console.log(args));	// 订阅一个 A 事件并传递回调函数
setTimeout(() => {
    pubsub.remove('A')
}, 500);
setTimeout(() => pubsub.emit('A', '这是 A 收到的第一个参数'), 1000)