观察者模式也成为了发布订阅模式,如下面三部分组成vue
上面介绍了组成可能你还有疑惑,下面就举一个例子,小明打算去售楼处去买一套房子,销售小姐告诉他这套住宅暂时没有房源,小明因而留了手机号码给他,某一天有房源的时候通知他。 上面例子中,发布者就是售楼中心,订阅者就是小明,消息队列就是小明留的手机号码,观察者模式可让对象松耦合在一块儿。es6
下面使用 ES6 的语法来写,若是没有基础,推荐看一遍 es6 入门再来app
上面介绍了定义,就根据上面的定义编写一个能够取消以及支持传递参数的观察者实例框架
const Event = new (class Watch {
constructor() {
// 消息队列
this.list = {};
}
// 订阅
subscribe(key, fn) {
if (!this.list[key]) {
// 避免重复
this.list[key] = new Set();
}
this.list[key].add(fn);
}
// 触发
trigger(key, ...args) {
const v = this.list[key];
if (!v) {
return;
}
v.forEach(f => f.apply(this, args), this);
}
// 删除,key是必须的
remove(key, fn) {
const v = this.list[key];
if (!v) {
return false;
}
if (v.has(fn)) {
return v.delete(fn);
}
// clear没有返回值,这里返回一个true
return v.clear() || true;
}
})();
Event.subscribe("abc", function(a) {
console.log(a); // x
});
Event.trigger("abc", "x");
复制代码
上面就实现了简单的观察者模式,不过能够观察上面代码能够发现观察者运行的机制是先订阅后发布,有没有办法相似于 QQ 消息同样,能够接收到离线消息,固然这个离线消息只能接收一次。优化
实现的思路很简单,就是经过一个离线消息队列,发布的时候判断这个离线消息队列存在么,若是存在,将消息存放在离线消息队列,当订阅的时候若是发现有离线消息队列就执行一次,以后清空网站
const Event = new (class Watch {
constructor() {
// 消息队列
this.list = {};
this.offLine = new Set();
}
// 订阅
subscribe(key, fn) {
if (!this.list[key]) {
// 避免重复
this.list[key] = new Set();
}
this.list[key].add(fn);
if (this.offLine) {
this.offLine.forEach(f => f(), this);
}
this.offLine = null;
}
// 触发
trigger(key, ...args) {
// 关键代码
const fn = () => {
const v = this.list[key];
if (!v) {
return;
}
v.forEach(f => f.apply(this, args), this);
};
if (this.offLine) {
this.offLine.add(fn);
}
fn();
}
// 删除,key是必须的
remove(key, fn) {
const v = this.list[key];
if (!v) {
return false;
}
if (v.has(fn)) {
return v.delete(fn);
}
// clear没有返回值,这里返回一个true
return v.clear() || true;
}
})();
Event.trigger("abc", "x");
Event.subscribe("abc", function(a) {
console.log(a); // x
});
复制代码
撒花,一个支持取消和先发布后订阅的观察者模式已经实现了,不过仍是有待优化的地方,好比命名,咱们能不能经过Event.created('zhangsan').trigger
的形式来调用呢?ui
动手写以前,咱们先缕清一下头绪this
created
先调用,好比直接就是Event.trigger
发布以后再订阅ok,下面就是针对上面问题实现spa
// 定义一个基础类,这个类是实现的核心层
class Basics {
_obj = {};
_default = "default";
_created(name = this._default) {
// 定义消息队列和离线消息队列
const list = {};
let offLine = new Set();
const then = this;
const obj = {
// 触发,若是第一次触发就添加到离线队列中
trigger(key, ...rest) {
const fn = () => {
const arr = [list, key, ...rest];
return then._trigger.apply(then, arr);
};
if (offLine) {
offLine.add(fn);
}
return fn();
},
// 添加订阅者同时执行离线队列
subscribe(key, fn) {
then._subscribe(...[list, key, fn]);
if (offLine) {
offLine.forEach(f => f());
}
offLine = null;
},
// 删除,key是必须的
remove(key, fn) {
const v = this.list[key];
if (!v) {
return false;
}
if (v.has(fn)) {
return v.delete(fn);
}
// clear没有返回值,这里返回一个true
return v.clear() || true;
}
};
// 判断命名来决定返回
return name
? this._obj[name]
? this._obj[name]
: (this._obj[name] = obj)
: obj;
}
// 触发
_trigger(l, k, ...args) {
const v = l[k];
if (!v || !v.size) {
return;
}
return Array.from(v, f => f.apply(this, args), this);
}
_subscribe(list, key, fn) {
if (!list[key]) {
list[key] = new Set();
}
list[key].add(fn);
}
}
// 这个是实现类
class Watch extends Basics {
constructor() {
// 必须,es6规定
super();
this.created = super._created;
}
trigger(key, ...rest) {
const v = this.created();
v.trigger(key, ...rest);
}
subscribe(key, fn) {
const v = this.created();
v.subscribe(key, fn);
}
remove(key, fn) {
const v = this.created();
v.remove(key, fn);
}
}
const Event = new Watch();
Event.trigger("abc");
Event.subscribe("abc", function() {
console.log(123);
});
Event.created("zhangsan").subscribe("abc", function(c) {
console.log(c);
});
Event.created("zhangsan").trigger("abc", 456);
复制代码
在实际中咱们使用观察者模式的例子也有不少,好比一个网站,分为导航和侧边,当用户信息更新的时候展现部分须要更换就能够用到,还有咱们用的框架,好比vue、React
等rest