前言
当我开始尝试去理解这两个概念..发现远远没那么简单,然后我又默默的再去理解了Vue的响应式,eventbus。。。后半部分偏向于场景原理实践例子。
有写的不清楚的地方,有些偏原理的文章,我也是去查阅的。有问题欢迎评论区建议。
好吧,正文开始,先放导图:
发布订阅模式和观察者模式
这个两个模式,有时候会有些分不清楚,今天借这篇文章理一下相关的知识点。
主要是从理念出发,到使用,到实现。
1. 发布订阅模式
1.2 什么是发布订阅模式?
来自搜索引擎的解释:
发布订阅者模式就是一种一对多的依赖关系。多个订阅者
(一般是注册的函数)同时监听同一个数据对象
,当这个数据对象发生变化的时候会执行一个发布事件,通过这个发布事件会通知到所有的订阅者,使它们能够自己改变对数据对象依赖的部分状态。
- 多个订阅者
- 同一个数据对象
- 消息管理的工作,当这个数据发生改变,执行一个发布事件,通知所以订阅者
这样看来,一个完整的订阅发布模式,由发布者、订阅者、消息管理器三部分组成。
1.3 实现一个发布订阅模式?
class PubSub {
constructor() {
this.subscribers = {} // 消息对象
}
subscribe(type, fn) { // 订阅
if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
}
unsubscribe(type, fn) { // 取消订阅
let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
this.subscribers[type] = listeners.filter(v => v !== fn);
}
publish(type, ...args) { // 发布
let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
listeners.forEach(fn => fn(...args));
}
}
let ob = new PubSub();
ob.subscribe('add', (val) => console.log(val)); // 给type为‘add’的属性上加一个发布函数
ob.publish('add', 1); // 当数据改变了,通知订阅,修改数据
- subscribe 函数,接收type,和一个fn
看看this.subscribers = {}这
个消息对象里面有没有type这个属性,如果没有就添加这个type,设置this.subscribers[type] = [];
否则,this.subscribers[type].push(fn);
,把订阅函数push进去
- 这里我们使用了Object.prototype.hasOwnProperty.call(),去判定属性是否是来自原型链。
- 使用原型链上真正的 hasOwnProperty 方法,因为javascript没有将hasOwnProperty作为一个敏感词,所以我们很有可能将对象的一个属性命名为hasOwnProperty
- 这样一来就无法再使用对象原型的 hasOwnProperty 方法来判断属性是否是来自原型链。
(像下面的这个例子👇🏻)
var jing = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
console.log(jing.hasOwnProperty('bar')); // false
var jing = {
hasOwnPropertyTrue: function() {
return false;
},
bar: 'Here be dragons'
};
console.log(jing.hasOwnProperty('bar')); // true
- unsubscribe 取消订阅
unsubscribe(type, fn) { // 取消订阅
let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
// 先要确定这个需要取消的订阅type是否存在,如果不存在或者type里面没订阅函数,直接返回
this.subscribers[type] = listeners.filter(v => v !== fn);
// 过滤掉对应type里面的某个需要取消订阅的函数
}
- publish 发布 某个type里面的订阅函数执行
publish(type, ...args) { // 发布
let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
// 先要确定这个需要取消的订阅type是否存在,如果不存在或者type里面没订阅函数,直接返回
listeners.forEach(fn => fn(...args));
// 执行这个type的订阅函数(和参数一起)
}
2. 观察者模式
2.1 什么是观察者模式
观察者模式,比发布订阅模式少了事件管理中心这个节点,目标和观察者可以直接进行交互,而发布订阅模式,由调度中心进行处理,订阅者和发布者互不干扰
- 目标
- 观察者
2.2 实现一个观察者模式?
class Subject {
constructor(name) {
this.name = name;
this.observers = [];
this.state = '状态1';
}
attach(observer) {
this.observers.push(observer)
}
changeState(newstate) {
this.state = newstate;
this.observers.forEach(o => o.update(newstate))
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(newstate) {
console.log(this.name, newstate)
}
}
let sub1 = new Subject('被观察者');
let obj1 = new Observer('观察者1');
let obj2 = new Observer('观察者2');
sub1.attach(obj1);
sub1.attach(obj2);
console.log(sub1.changeState('状态2'))
// 观察者1 状态2
// 观察者2 状态2
观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,会造成代码的冗余。
而发布订阅模式则统一由调度中心处理,消除了发布者和订阅者之间的依赖。
3. 场景复现
3.1 javascript 原生事件的观察者模式
ele.addEventListener('click', () => {});
addEventListener就相当于注册了一个观察者,当观察到‘click’事件的时候,作出一些处理。
3.2 vue 中的发布订阅使用 eventBus
(暂时不考虑vuex之类的状态管理器传递数据)
当我们希望5的数据能传到6的时候,我们需要走a-b-c-d的路线,这个组件传值的方法有些麻烦。
我们可以使用vue中的off,$emit,实现发布订阅模式
创建一个事件中心绑定到每个组件上面,只有一个组件有新的数据发布到上面,其他已经订阅的组件就可以同步获取
组件的状态被放在事件中心里面,当状态发生改变了,事件中心会通知那些订阅了状态的组件进行状态更新。
创建全局EventBus
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
使用:
this.$bus.$emit() // 发布
this.$bus.$on() // 订阅
this.$bus.$off() // 取消订阅
EventBus
可以较好的实现兄弟组件之间的数据通讯。
3.3 vue 中的数据劫持+发布订阅+观察者模式
在谈到vue的响应式原理的时候,我们会说到vueJS采用数据劫持结合发布订阅模式。
- 数据劫持: Object.defineProperty()来劫持data中各个属性的setter、getter (setter和getter是对象的存储器属性,是一个函数,用来获取和设置值)
- 发布订阅模式: 在数据变动时,发布消息给订阅者,触发响应的监听回调
vue中dep和watcher示例,watcher订阅dep,dep通知watcher执行
update
- dep.js:
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) { //添加订阅者
this.subs.push(sub)
}
removeSub (sub: Watcher) { //删除订阅者
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () { //通知订阅者
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() //订阅者update方法
}
}
}
- watcher.js中的update方法
update () {
//watcher作为订阅者的update方法
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
3.4 nodejs
Node 自身提供 events 模块,是发布/订阅模式的一个简单实现,Node 中部分模块都继承自它。
实例
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on("event-titus", function (message) {
console.log(message);
})
emitter.emit("event-titus", "I am titus");
// off once
EventEmitter
- on 绑定 订阅
- emit 发布
- off 取消订阅
- once 只在发布的时候执行一次
4. 总结对比
发布订阅模式属于广义上的观察者模式,发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式
在来看看两种模式的对比:
- 发布订阅:通由调度中心进行处理,订阅者和发布者互不干扰
- 观察者: 目标和观察者可以直接进行交互
有写的不当的地方欢迎评论区指正❤
文章就到这里了
我是婧大
一个在默默准备秋招的大四学仔。
希望可以和你一起学习一起进步呀
wx: lj18379991972
欢迎👏🏻加好友,一起学前端。❤