一、 什么是发布-订阅模式
1. 定义
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
2. 例子
我们关注的公众号文章,给我们进行推送时候,即是发布订阅模式
公众号属于发布者,我们属于订阅者
用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。
二、 如何实现发布-订阅模式?
1. 实现思路
- 创建一个对象
- 在该对象上创建一个调度中心
- on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
- emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
- off 方法可以根据 event 值取消订阅(取消订阅)
- once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
2. 代码
// 公众号调度中心 class EventHub { constructor(){ // 缓存列表,存放 event 及 fn this.map = {}; } on(event, fn) { // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表 this.map[event] = this.map[event] || [] // 把 fn 添加到对应 event 的缓存列表里 this.map[event].push(fn) } emit(event, data) { const fnList = this.map[event] || [] // 如果缓存列表里没有 fn 就return if (!fnList || fnList.length === 0) return; // 遍历 event 值对应的缓存列表,依次执行 fn fnList.forEach(fn => fn.call(undefined, data)) } off(event, fn) { const fnList = this.map[event] || [] const index = fnList.indexOf(fn) // 如果缓存列表里没有要找的fn 就return if(index < 0) return fnList.splice(index, 1) } //执行一次 once(event, callback){ const f = (data) => { callback(data); this.off(event, f); } this.on(event, f); } } function user1 (content) { console.log('用户1订阅了:', content); }; function user2 (content) { console.log('用户2订阅了:', content); }; function user3 (content) { console.log('用户3订阅了:', content); } function user4 (content) { console.log('用户4订阅了:', content); } const e = new EventHub() // 订阅 e.on('article1', user1); e.on('article1', user2); e.on('article1', user3); // 取消user2方法的订阅 e.off('article1', user2); e.once('article2', user4) // 发布 e.emit('article1', 'Javascript 发布-订阅模式'); e.emit('article1', 'Javascript 发布-订阅模式'); e.emit('article2', 'Javascript 观察者模式'); e.emit('article2', 'Javascript 观察者模式'); /* 用户1订阅了: Javascript 发布-订阅模式 用户3订阅了: Javascript 发布-订阅模式 用户1订阅了: Javascript 发布-订阅模式 用户3订阅了: Javascript 发布-订阅模式 用户4订阅了: Javascript 观察者模式 */
三、 总结
1. 优点
- 对象之间解耦
- 异步编程中,可以更松耦合的代码编写
2. 缺点
- 创建订阅者本身要消耗一定的时间和内存
- 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护