关于观察者模式与发布/订阅模式,很多大神都有帖子对他们作出了解释,可是不少文章都将二者混在了一块儿,认为他们就是同一种模式,实际上这二者仍是有些差别的,因此本文就从我在谷歌的查阅和我的的理解,来仔细讲讲这两种模式,已经他们的一些应用场景。javascript
官方给出的观察者模式的解释是这样的:html
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都获得通知并被自动更新。vue
观察者模式实现的,其实就是当目标对象的某个属性发生了改变,全部依赖着目标对象的观察者都将接到通知,作出相应动做。 因此在目标对象的抽象类里,会保存一个观察者序列。当目标对象的属性发生改变生,会从观察者队列里取观察者调用各自的方法。java
下面经过一张图来看一下观察者模式的实现。node
class Subject {
let observers = [];
let state;
getState() {
return this.state;
}
setState(state) {
this.state = state;
notifyAllObservers();
}
attach(observer){
observers.push(observer);
}
notifyAllObservers(){
for (observer in observers) {
observer.update();
}
}
}
class Observer {
let subject;
update();
}
class BinaryObserver extends Observer {
constructor(subject) {
super();
subject.attach(this);
}
update() {
console.log("Binary");
}
}
class OctalObserver extends Observer {
constructor(subject) {
super();
subject.attach(this);
}
update() {
console.log("Octal");
}
}
var subject = new Subject();
var binaryObserver = new BinaryObserver(subject);
var octalObserver = new OctalObserver(subject);
subject.setState(15);
//Binary
//Octal
复制代码
在不少文章里讲到的观察者模式,其实说的都是发布订阅模式,那么他们的差异到底在哪里呢,让咱们一点点往下看。 维基中对于发布/订阅是这样描述的:设计模式
发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不一样的类别,无需了解哪些订阅者(若是有的话)可能存在。一样的,订阅者能够表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(若是有的话)存在。缓存
也就是说,发布/订阅模式和观察者最大的差异就在于消息是否经过一个中间类进行转发。bash
由上,咱们就能够得出这二者的区别了:服务器
同步
的场景,而发布/订阅模式大多用于异步
场景,例如消息队列。到这里,确定会有小伙伴问,为何没有发布/订阅模式的代码实例。其实在不少JS框架中,都采用发布/订阅模式进行了很多设计,下面咱们就从Vue和Node来深刻讲一讲关于发布/订阅的使用。网络
Vue中使用到发布/订阅模式最经典的两块实现就是数据双向绑定
和父子组件通讯
。
vue数据双向绑定是经过数据劫持结合发布者-订阅者模式的方式来实现的。 具体实现数据双向绑定会须要三个步骤:
Vue中,利用 Object.defineProperty()
实现数据劫持,监听到数据的变化。
Object.defineProperty(data, key, {
set: function (value) {
//...
},
get: function () {
//...
}
})
复制代码
Observer是一个数据监听器,用来监听全部的属性。
function Observer(data) {
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
var self = this;
//遍历对象,得到对象全部属性的监听
Object.keys(data).forEach(function(key) {
self.defineReactive(data, key, data[key]);
});
},
defineReactive: function(data, key, val) {
var dep = new Dep();
// 递归遍历全部子属性
var childObj = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function getter () {
if (Dep.target) {
// 在这里添加一个订阅者,有关Dep.target的得到,会在watcher中实现
dep.addSub(Dep.target);
}
return val;
},
// setter,若是对一个对象属性值改变,就会触发setter中的dep.notify(),通知watcher(订阅者)数据变动,执行对应订阅者的更新函数,来更新视图。
set: function setter (newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 新的值是object的话,进行监听
childObj = observe(newVal);
dep.notify();
}
});
}
};
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
// 消息订阅器Dep,订阅器Dep主要负责收集订阅者,而后在属性变化的时候执行对应订阅者的更新函数
function Dep () {
this.subs = [];
}
Dep.prototype = {
/**
* [订阅器添加订阅者]
* @param {[Watcher]} sub [订阅者]
*/
addSub: function(sub) {
this.subs.push(sub);
},
// 通知订阅者数据变动
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
复制代码
watcher就是一个订阅者,里面包含了添加订阅者到消息队列和接收响应发布者的通知。
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将本身添加到订阅器的操做
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存本身
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 释放本身
return value;
}
};
复制代码
参数解释:
function SelfVue (data, el, exp) {
this.data = data;
observe(data);
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this;
}
<body>
<h1 id="name">{{name}}</h1>
</body>
<script src="js/observer.js"></script>
<script src="js/watcher.js"></script>
<script src="js/index.js"></script>
<script type="text/javascript">
var ele = document.querySelector('#name');
var selfVue = new SelfVue({
name: 'hello world'
}, ele, 'name');
window.setTimeout(function () {
console.log('name值改变了');
selfVue.data.name = 'canfoo';
}, 2000);
</script>
复制代码
其实到这里咱们就已经实现了vue的数据双向绑定,从这个绑定过程,咱们也很明确看到发布/订阅模式是如何起做用的。 本文主要围绕两种设计模式展开,有关compile解析节点的部分,在这里就不作细讲,感兴趣的小伙伴能够继续深刻源码探究。
Vue的父子组件通讯也用到了发布/订阅模式。
$on
订阅观察特定事件$emit
将变化广播给其余订阅观察对应事件的组件,并调用他们的方法Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
复制代码
Node中有一个EventEmiter模块,其消息机制采用的就是发布/订阅思想,下面咱们来手写一个EventEmiter类。
class EvenEmiter{
construct() {
this._events = {};
this.defaultMaxListener = 10;
}
setMaxListner(n) {
this._maxListeners = n;
}
getMaxListener() {
return this._maxListeners ? this.maxListeners : this.defaultMaxListeners;
}
once(eventName, callback) {
wrap(...args) {
callback(...args);
this.removeListener(eventName,callback);
}
wrap.cb = callback;
this.on(eventName, wrap);
}
on(eventName, callback) {
if (!this._events) {
this._events = {}
}
if (this._events[eventName]) {
this._events[eventName].push(callback);
}
else {
this._events[eventName] = [callback];
}
}
emit(eventName) {
if (this._events[eventName]) {
this._events[eventName].forEach((fn) => {
fn()
});
}
}
removeListener(eventName, callback) {
if (this._events[eventName]) {
this._events = this._events.filter(fn => {
return fn !== callback;
})
}
}
addEvnetListener(eventName, callback) {
this.on(eventName, callback);
}
}
复制代码
以上就是有关观察者模式和发布/订阅模式的所有内容,若是有补充和有错的地方,欢迎你们留言。
参考连接: vue的双向绑定原理及实现 node 订阅发布及实现