## 观察者和发布订阅模式的区别
观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。
### 一、观察者模式(Observer)
观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。
![avatar](./pic/subject.png)
在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。
**封装观察者模式 -- observer.js**
```javascript
// 定义一个主体对象
class Subject {
constructor() {
//保存所有的观察者对象
this.Observers = [];
}
add(observer) { //添加观察者对象
this.Observers.push(observer)
}
remove(observer) {//移除观察者对象
this.Observers.filter(item => item === observer);
}
//Subject 对象通知一系列 Observer 对象进行更新
notify() {
// item :这里是具体的某个观察者对象
this.Observers.forEach(item => {
item.update();
})
}
}
//定义观察着对象
class Observer {
constructor(name) {
this.name = name;
}
update() {
console.log(`my name is:${this.name}`);
}
}
```
**测试观察者模式-- index.html**
```javascript
//测试 主体 -- 2个观察者
let sub = new Subject();
let obs1 = new Observer(‘leaf111‘);
let obs2 = new Observer(‘leaf222‘);
sub.add(obs1);
sub.add(obs2);
sub.notify();
```
上述代码中,我们创建了 Subject 对象和两个 Observer 对象,当有关状态发生变更时则通过 Subject 对象的 notify 方法通知这两个 Observer 对象,这两个 Observer 对象通过 update 方法进行更新。
### 二、发布订阅模式(Publisher && Subscriber)
在“发布者-订阅者”模式中,称为发布者的消息发送者不会将消息编程为直接发送给称为订阅者的特定接收者。这意味着发布者和订阅者不知道彼此的存在。存在第三个组件,称为事件调度中心或消息代理,它由发布者和订阅者都知道,它过滤所有传入的消息并相应地分发它们。
**换句话说,发布订阅模式是用于在不同系统组件之间传递消息的模式,而这些组件不知道关于彼此身份的任何信息**。
最简单的发布订阅模式
btn是订阅者
btn.addEventListener(‘click‘, () => {});
addEventListener就相当于订阅一个click事件,通过用户点击的时候去触发。 或者 btn.click()自动触发
![avatar](./pic/sub2.png)
**封装发布订阅模式 -- event.js**
```javascript
class Event {
constructor(){
//存储的所有的订阅者
this.events = {}
//用来存放事件监听函数
// {
// event1:[f1,f2,f3],
// event2:[f4,f5]
// }
}
//订阅事件
on(type,fn) {
if(!this.events[type]) {
this.events[type] = [];
};
this.events[type].push(fn)
}
//发布消息
emit(type,...args){
if(!this.events[type]){
return;
}
this.events[type].forEach((fn)=>{
fn.call(this,...args);
})
}
//取消订阅了某个事件的订阅者
off(type,fn){
if(!this.events[type]){
return;
}
if(!fn){
this.events[type] = []; //对订阅type事件的所有回调清空
return;
}
var index = this.events[type].indexOf(fn);
this.events[type].splice(index,1);
}
//只监听一次
once(type,fn){
// on+off结合,先订阅,再取消
//定于onlyFn ,先对回调 onlyFn进行订阅,再对回调onlyFn进行取消订阅,即删除
var onlyFn = ()=>{
fn.apply(this,arguments); //方式1
// fn.call(this,...arguments); //方式二
this.off(type,onlyFn) //用on监听的onlyFn回调删除
}
this.on(type,onlyFn)
}
}
```
**测试发布订阅模式 -- index.html**
```javascript
<script>
var ev = new Event();
ev.once(‘add‘,function(data,ooo){
console.log(data)
})
ev.on(‘reduce‘,function(data){
console.log(data)
})
ev.off(‘reduce‘);
ev.emit(‘add‘,{name:‘laney‘,age:20})
ev.emit(‘reduce‘,{name:‘song‘,age:20})
</script>
```
### 三、观察者和发布订阅模式的区别
这两种模式之间的主要区别可以如下所示:
![avatar](./pic/sub3.png)
如上图所示:
1、在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理(也可叫调度中心)进行通信。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。
2、在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
3、观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
### 四、观察者模式在vue中的作用
Vue会遍历实例的data属性,把每一个data都设置为访问器,然后在该属性的getter函数中将其设为watcher,在setter中向其他watcher发布改变的消息。
```javascript
//遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性
function observe(obj,vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm,key,obj[key]);
});
}
//设置为访问器属性,并在其getter和setter函数中,使用订阅发布模式。互相监听。
function defineReactive(obj,key,val){
//这里用到了观察者模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
//实例化一个主题对象,对象中有空的观察者列表
var dep = new Dep();
//将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同
//所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法
Object.defineProperty(obj,key,{
get: function(){
//Dep.target指针指向watcher,增加观察者watcher到主体对象Dep
if(Dep.target){
dep.addSub(Dep.target);
}
return val;
},
set: function(newVal){
if(newVal === val){
return
}
val = newVal;
//console.log(val);
//给观察者列表中的watchers发出通知
dep.notify();
}
});
}
//主题对象Dep构造函数
function Dep(){
this.subs = []; //观察者列表
}
//Dep有两个方法,增加观察者和发布消息
Dep.prototype = {
addSub: function(sub){
this.subs.push(sub);
},
notify: function(){
this.subs.forEach(function(sub){
sub.update();
});
}
}
```
## 五、Vue 中非父子组件组件通信
在 Vue 中不同组件之间通讯,有一种解决方案叫Event Bus,这其实就是发布订阅模式的实现,非常简单好用。
简单演示:
```javascript
<body>
<div id="app">
<button type="button" @click=‘sendMsg‘>发送数据</button>
</div>
<script src="https://cdn.bootcss.com/vue/2.3.4/vue.js"></script>
<script>
var EventBus = new Vue();
new Vue({
el:‘#app‘,
data:{
name:‘laney‘
},
created(){
EventBus.$on(‘getData‘,(data)=>{
console.log(data);
})
},
methods:{
sendMsg(){
EventBus.$emit(‘getData‘,{
name:‘laney‘,
likes:‘shopping‘
})
}
}
})
</script>
</body>
```
父子组件之间传值:
父传子 : props
子传父: 先在父组件里调用子组件的地方 监听事件 ,然后在子组件里通过$emit发送数据
自己通过发布订阅模式封装一个eventEmitter