学习和总结文章同步发布于 https://github.com/xianshanna...,有兴趣能够关注一下,一块儿学习和进步。
首先咱们须要了解二者的定义和实现的方式,才能更好的区分二者的不一样点。html
或许之前认为订阅发布模式是观察者模式的一种别称,可是发展至今,概念已经有了很多区别。前端
在 软件架构中, 发布-订阅是一种 消息 范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不一样的类别,无需了解哪些订阅者(若是有的话)可能存在。一样的,订阅者能够表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(若是有的话)存在。
或许你用过 eventemitter
、node 的 events
、Backbone 的 events
等等,这些都是前端早期,比较流行的数据流通讯方式,即订阅发布模式。node
从字面意思来看,咱们须要首先订阅,发布者发布消息后才会收到发布的消息。不过咱们还须要一个中间者来协调,从事件角度来讲,这个中间者就是事件中心,协调发布者和订阅者直接的消息通讯。git
完成订阅发布整个流程须要三个角色:github
以事件为例,简单流程以下:数组
发布者->事件中心<=>订阅者,订阅者须要向事件中心订阅指定的事件 -> 发布者向事件中心发布指定事件内容 -> 事件中心通知订阅者 -> 订阅者收到消息(多是多个订阅者),到此完成了一次订阅发布的流程。架构
简单的代码实现以下:app
class Event { constructor() { // 全部 eventType 监听器回调函数(数组) this.listeners = {} } /** * 订阅事件 * @param {String} eventType 事件类型 * @param {Function} listener 订阅后发布动做触发的回调函数,参数为发布的数据 */ on(eventType, listener) { if (!this.listeners[eventType]) { this.listeners[eventType] = [] } this.listeners[eventType].push(listener) } /** * 发布事件 * @param {String} eventType 事件类型 * @param {Any} data 发布的内容 */ emit(eventType, data) { const callbacks = this.listeners[eventType] if (callbacks) { callbacks.forEach((c) => { c(data) }) } } } const event = new Event() event.on('open', (data) => { console.log(data) }) event.emit('open', { open: true })
Event 能够理解为事件中心,提供了订阅和发布功能。dom
订阅者在订阅事件的时候,只关注事件自己,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件自己,而不关心谁订阅了这个事件。函数
观察者模式定义了一种一对多的依赖关系,让多个 观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知全部 观察者对象,使它们可以自动更新。
观察者模式咱们可能比较熟悉的场景就是响应式数据,如 Vue 的响应式、Mbox 的响应式。
观察者模式有完成整个流程须要两个角色:
简单流程以下:
目标<=>观察者,观察者观察目标(监听目标)-> 目标发生变化-> 目标主动通知观察者。
简单的代码实现以下:
/** * 观察监听一个对象成员的变化 * @param {Object} obj 观察的对象 * @param {String} targetVariable 观察的对象成员 * @param {Function} callback 目标变化触发的回调 */ function observer(obj, targetVariable, callback) { if (!obj.data) { obj.data = {} } Object.defineProperty(obj, targetVariable, { get() { return this.data[targetVariable] }, set(val) { this.data[targetVariable] = val // 目标主动通知观察者 callback && callback(val) }, }) if (obj.data[targetVariable]) { callback && callback(obj.data[targetVariable]) } }
可运行例子以下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover" /> <title></title> </head> <body> <div id="app"> <div id="dom-one"></div> <br /> <div id="dom-two"></div> <br /> <button id="btn">改变</button> </div> <script> /** * 观察监听一个对象成员的变化 * @param {Object} obj 观察的对象 * @param {String} targetVariable 观察的对象成员 * @param {Function} callback 目标变化触发的回调 */ function observer(obj, targetVariable, callback) { if (!obj.data) { obj.data = {} } Object.defineProperty(obj, targetVariable, { get() { return this.data[targetVariable] }, set(val) { this.data[targetVariable] = val // 目标主动通知观察者 callback && callback(val) }, }) if (obj.data[targetVariable]) { callback && callback(obj.data[targetVariable]) } } const obj = { data: { description: '原始值' }, } observer(obj, 'description', value => { document.querySelector('#dom-one').innerHTML = value document.querySelector('#dom-two').innerHTML = value }) btn.onclick = () => { obj.description = '改变了' } </script> </body> </html>
角色角度来看,订阅发布模式须要三种角色,发布者、事件中心和订阅者。二观察者模式须要两种角色,目标和观察者,无事件中心负责通讯。
从耦合度上来看,订阅发布模式是一个事件中心调度模式,订阅者和发布者是没有直接关联的,经过事件中心进行关联,二者是解耦的。而观察者模式中目标和观察者是直接关联的,耦合在一块儿(有些观念说观察者是解耦,解耦的是业务代码,不是目标和观察者自己)。
优缺点都是从前端角度来看的。
因为订阅发布模式的发布者和订阅者是解耦的,只要引入订阅发布模式的事件中心,不管在何处均可以发布订阅。同时订阅发布者相互之间不影响。
订阅发布模式在使用不当的状况下,容易形成数据流混乱,因此才有了 React 提出的单项数据流思想,就是为了解决数据流混乱的问题。
灵活是有点,同时也是缺点,使用不当就会形成数据流混乱,致使代码很差维护。
订阅发布模式须要维护事件列队,订阅的事件越多,内存消耗越大。
目标变化就会通知观察者,这是观察者最大的有点,也是由于这个优势,观察者模式在前端才会这么出名。
相比订阅发布模式,因为目标和观察者是耦合在一块儿的,因此观察者模式须要同时引入目标和观察者才能达到响应式的效果。而订阅发布模式只须要引入事件中心,订阅者和发布者能够再也不一处。