>[success] # 发布订阅和观察者模式 1. 发布订阅模式的构成三部分,`订阅者`、`发布者`、`信号中心` 2. 假定 存在一个`信号中心`,某个任务执行完成,就向信号中心`发布(publish)`一个信号,其他任务可以向信号中心`订阅(subscribe)`这个信号,从而知道什么时候自己可以开始执行。这就叫做**发布/订阅模式(publish-subscribe pattern)** 3. 简单的理解**信号中心**其实有两个核心功能**注册事件,触发事件**。**订阅者通过在信号中心注册事件,发布者通过发布信号触发信号中心的触发事件**。让订阅者接收到触发信息 >[info] ## 观察者 一般常见的定义名词解释 1. **观察者(订阅者) - Watcher** * `update()`: 当事件发生时,具体要做的事情 2. **目标(发布者) - Dep 知道观察者的存在** * `subs` 数组: 存储所有的观察者 * `addSub()`: 添加观察者 * `notify()`: 当事件发生,调用所有观察者的 update() 方法 3. 观察者和发布订阅的最大区别**没有事件中心**,观察者模式是通过发布者进行**进行记录观察者和调用观察者** ~~~ // 发布者 class Dep { constructor() { this.subs = [] } // 记录观察者 addSub(sub) { if (sub && sub.update) { this.subs.push(sub) } } // 发布通知 notify() { this.subs.forEach(sub => { sub.update() }) } } // 观察者具备updata 方法 // 订阅者-观察者 class Watcher { update() { console.log('update') } } // 测试 let dep = new Dep() let watcher = new Watcher() dep.addSub(watcher) dep.notify() ~~~ >[danger] ##### 观察者模式和发布/订阅模式区别 1. **观察者模式**是由具体目标调度,比如当事件触发,Dep就会去调用观察者方法,所以观察者模式的订阅者与发布者之间存在依赖关系 2. **发布/订阅模式**由统一调度中心调用,因此发布者和订阅者不需要知道对方存在,隔离两者且减少两者之间的关系 3. **通过发布订阅 和 观察者 中二者对订阅者的存储结构来看**,发布定订阅由于大家共享一个事件中心因此为了区分每个订阅者想要订阅的类型因此采用的是对象形式`{evnet:[....],evnet2:[....]}` **观察者**只是为自己的那一类订阅者服务因此他的形式是数组`[]` 简单的说,**发布定订阅是所有订阅者的集合并且利用key 进行分类**,而观察者是一类订阅者,如果这类订阅者不是此类观察者,需要创建新的观察者对这类订阅者进行订阅 和 发布 ![](https://img.kancloud.cn/2b/c4/2bc4fcb312e982ac4dbbfd827ddf2919_768x546.png) >[info] ## vue 中发布订阅 1.vue中自定义事件就是利用了**发布订阅模式** ~~~ // 订阅中心 const vm = new Vue({}) // 注册事件 vm.$on('dataChage', (msg) => { console.log(msg) // 测试数据 }) // 触发事件 vm.$emit('dataChage', '测试数据') 2.下面案例把调用信号中心注册事件的叫'订阅者',使用信号中心触发事件叫'发布者' // eventBus.js // 事件中心 let eventHub = new Vue() // ComponentA.vue // 发布者 addTodu: function () { // 发布消息(事件) 触发事件 eventHub.$emit('add-todo', { text: this.newTodoText }) this.newTodoText = '' } // ComponentB.vue // 订阅者 created: function () { // 注册事件 eventHub.$on('add-todo', this.addTodo) } ~~~ >[danger] ##### 模拟 Vue 自定义事件的实现 ~~~ // 事件中心 class EventEmitter { constructor() { // { 'click': [fn1, fn2], 'change': [fn] } 键为事件名称 值为存储事件处理函数的数组 this.subs = Object.create(null) } // 注册事件,需要事件名和事件方法因此需要两个参数 $on(eventType, handler) { this.subs[eventType] = this.subs[eventType] || [] this.subs[eventType].push(handler) } // 触发事件,触发事件是在注册事件中标记的 // 因此第一个参数触发事件名 第二个参数是传递的参数 $emit(eventType, ...params) { this.subs[eventType] && this.subs[eventType].forEach(handler => { handler(...params) }) } } let em = new EventEmitter() em.$on('click', (val) => { console.log('click1', val) }) em.$on('click', (val) => { console.log('click2', val) }) em.$emit('click', '测试') // 打印结果 click1 测试 click2 测试 ~~~