最近突然因为工作涉及到这一块的内容,网上搜罗了一些文章,有了一些自己的理解,遂记📝。
前言
定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。
早期只是 观察者模式的一种别称,后面慢慢的发展独立开来,成为一种不同的模式。
发布订阅模式描述了对象,典型的应用就是 eventEmmiter ,主要实现了事件的调度,主要包含三个角色
- 发布者:只负责发布事件,不关心会被谁什么时候调用。
- 调度中心: 存取事件
- 订阅者 :只负责订阅事件,不关心事件是什么时候被谁发布的。
做个类比方便理解:就像某些平台(消息代理或调度中心或中间件,如抖音)你(用户,订阅者)关注了某些主播或up,(发布者),他们只负责往平台发布他们的动态,而你(用户)就会收到他们的推送。
另外 MQTTjs 主要利用的就是发布订阅模式 推荐 vue中使用mqtt详细教程(兼容ie)?
实现
观察者模式
基本实现
对于对象 subject , 所有观察者(Observer )都对其都依赖的关系。对于 subject 的一切变化都会自动出发依赖它(subject )的观察者。
class Observer {
id = 0
constructor() {
++this.id;
}
update(val) {
console.log(this.id + ' updated: ', val)
}
}
class ObserverList {
constructor() {
this.observerList = []
}
add(observer) {
return this.observerList.push(observer);
}
remove(observer) {
this.observerList = this.observerList.filter(ob => ob !== observer);
}
count() {
return this.observerList.length;
}
get(index) {
return this.observerList[index];
}
}
class Subject {
constructor() {
this.observers = new ObserverList();
}
addObserver(observer) {
this.observers.add(observer);
}
removeObserver(observer) {
this.observers.remove(observer);
}
notify(...args) {
let obCount = this.observers.count();
for (let index = 0; index < obCount; index++) {
this.observers.get(index).update(...args);
}
}
}
const sub = new Subject();
const obs1 = new Observer();
sub.addObserver(obs1);
sub.notify('假装有变化1')
const obs2 = new Observer();
sub.addObserver(obs2);
sub.notify('假装有变化2')
实际应用场景
vue中将 数据作为被观察者 (subject),利用 defineProperty / Proxy 拦截,一旦发生任何变化都通知到 他的依赖者(上文的 Observer, 下面代码中的 Dep) ,然后触发相应的更新。其中相关代码 猛戳这里? 如下。
id 自增是为了防止 添加 依赖(dep)时避免重复的检测标识
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
if (!config.async) {
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
function remove (arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1)
}
}
}
发布订阅者模式
基本实现
主要实现 订阅 ,取消订阅 ,发布 。
- 订阅: 为 某种类型事件触发时 添加一个操作(fn)
- 取消订阅: 取消 某种类型事件触发时 的某个操作(fn)
- 发布:触发某种类型事件。
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', (...args) => {
const val = [...args].reduce((acc, cur) => acc += cur, 0)
console.log('add: ', val)
});
ob.publish('add', 1,3);
前面说过,发布订阅模式的额典型的应用就是 eventEmmiter 。
eventEmmiter
对象实现
let event = {
list: {},
on(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
},
once (key, fn) {
let _this = this;
_this.on(key, () => {
fn();
_this.off(key, fn)
});
},
emit(...args) {
let key = [].shift.call([...args]),
events = this.list[key];
if (!events || fns.length === 0) {
return false;
}
events.forEach(evt => {
evt.apply(this, args);
});
},
off(key, evt) {
let events = this.list[key];
if (!events) return false;
if (!evt) {
events && (events.length = 0);
} else {
events.forEach((cb, i) => {
if (cb === evt) {
events.splice(i, 1);
}
});
}
}
};
Class实现
class EventEmitter {
constructor() {
this.cache = {}
}
on(event, fn) {
if (this.cache[event]) {
this.cache[event].push(fn)
} else {
this.cache[event] = [fn]
}
}
emit(event, once = false, ...args) {
if (this.cache[event]) {
let tasks = this.cache[event].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[event]
}
}
}
off(key, fn) {
let events= this.cache[key]
if(!events) return false;
if(!fn) {
events.length = 0;
}else{
events.forEach((cb, i) => {
if (cb === evt) {
events.splice(i, 1);
}
});
}
}
}
Vue2.x中的实现
用过vue的同学知道,vue 有一个 简单的通信模式 是利用 $emit 与 $on 实现,其实就是基于发布订阅者模式,以下为相关代码。
删除了部分注释与边界代码,想看更多的话 猛戳这里?
function eventsMixin (Vue) {
var hookRE = /^hook:/;
Vue.prototype.$on = function (event, fn) {
var vm = this;
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn);
}
return vm
};
Vue.prototype.$once = function (event, fn) {
var vm = this;
function on () {
vm.$off(event, on);
fn.apply(vm, arguments);
}
on.fn = fn;
vm.$on(event, on);
return vm
};
Vue.prototype.$off = function (event, fn) {
var vm = this;
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
if (Array.isArray(event)) {
for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
vm.$off(event[i$1], fn);
}
return vm
}
var cbs = vm._events[event];
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null;
return vm
}
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break
}
}
return vm
};
Vue.prototype.$emit = function (event) {
var vm = this;
var cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
var info = "event handler for \"" + event + "\"";
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm
};
轻量级库 mitt 的实现
这是 vue 官方推荐的库 tiny-emitter? ,下面为其实现。
另外还有个纯 TypeScript 实现的库 mitt?
非常简单精悍👍👍👍
function E () {
}
E.prototype = {
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
emit: function (name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
发布订阅模式与观察者模式的关系
其实这两种模式不过是实现上的差异,发布订阅模式多了个事件池。
其实说不同也不同(间接通知与直接通知),说相同也相同(都是发布更新,获取更新的过程)。个人更愿意将它们理解为同一种模式的不同实现。
如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级 --------发布订阅模式与观察者模式? 
参考
站在别人肩膀上能看的更远,谢谢💖💖💖以下文章作者~~~ 🔗JS兵法36 计,你会多少?
🔗JavaScript 发布-订阅模式
🔗发布订阅模式,在工作中它的能量超乎你的想象
🔗JavaScript 发布-订阅模式
🔗观察者模式和发布订阅模式的区别
🔗谈谈JS的观察者模式(自定义事件)
|