如果说,你只能掌握一种设计模式,那必须毫不犹豫的选择观察者模式,也叫发布订阅模式。
es6中promise的实现、vue中双向绑定原理的实现、Node.js中的 EventEmitter事件监听器等等都应用到了观察者模式,可见其应用之广泛
那接下来具体看一下观察者模式到底是什么
介绍
一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
优点
对象之间解耦,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变
缺点
1.创建订阅者本身会消耗一定时间和内存。当订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中
2.多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护。虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。
所以要根据实际业务场景合理使用。
实现
来个通俗一点的解释吧。小明最近在找工作,好不容易约到一家心仪的公司A面试,结果到了以后发现职位不匹配。但是小明不甘心,就告诉HR什么时候有匹配的岗位就告诉我,天荒地老我都等,HR说那也行,你关注一下我们公司的公众号,有消息我就发在那个上了。同时又有小红、小李都来面试,情况也和小明一样。HR心想随便你们多少人,我到时候在公众号上发一下消息就行了,到时候你们爱来不来。那当然了小明也不管你HR到底是谁,只要你有岗位发消息告诉我就行。
在上述栗子中,HR就是发布者,求职者就是订阅者
基础版
let corp = {}; // 自定义一个公司对象
// 这里放一个列表用来缓存回调函数
corp.list = [];
// 去订阅事件
corp.on = function (fn) {
// 二话不说,直接把fn先存到列表中
this.list.push(fn);
};
// 发布职位
corp.emit = function () {
// 当发布的时候再把列表里存的函数依次执行
this.list.forEach(cb => {
cb.apply(this, arguments);
});
};
// 小明关注公众号
corp.on(function (position, salary) {
console.log('需求职位是:' + position);
console.log('岗位薪水:' + salary);
});
// 小红关注公众号
corp.on(function(position, salary) {
console.log('新的需求职位是:' + position);
console.log('新的岗位薪水:' + salary);
});
corp.emit('前端', 10000);
corp.emit('设计', 6000);
需求职位是:前端
岗位薪水:10000
新的需求职位是:前端
新的岗位薪水:10000
需求职位是:设计
岗位薪水:6000
新的需求职位是:设计
新的岗位薪水:6000
很明显这样的结果并不是我们想要的,对于小明来说只想知道和前端相关的信息,小红也只关注设计的岗位,简单改造一下
改进版
let corp = {};
// 这次换成一个对象类型的缓存列表
corp.list = {};
corp.on = function(key, fn) {
// 如果对象中没有对应的key值
// 也就是说明没有订阅过
// 那就给key创建个缓存列表
if (!this.list[key]) {
this.list[key] = [];
}
// 把函数添加到对应key的缓存列表里
this.list[key].push(fn);
};
corp.emit = function() {
// 第一个参数是对应的key值
// 直接用数组的shift方法取出
let key = [].shift.call(arguments),
fns = this.list[key];
// 如果缓存列表里没有函数就返回false
if (!fns || fns.length === 0) {
return false;
}
// 遍历key值对应的缓存列表
// 依次执行函数的方法
fns.forEach(fn => {
fn.apply(this, arguments);
});
};
// 小明关注公众号
corp.on('join', (position, salary) => {
console.log('需求职位是:' + position);
console.log('岗位薪水:' + salary);
});
// 小红关注公众号
corp.on('other', (skill, hobby) => {
console.log('新的需求职位是:' + skill);
console.log('新的岗位薪水:' + hobby);
});
corp.emit('join', '前端', 10000);
corp.emit('other', '设计', 6000);
需求职位是:前端
岗位薪水:10000
新的需求职位是:设计
新的岗位薪水:6000
最终版
let event = {
list: {},
on(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
},
emit() {
let key = [].shift.call(arguments),
fns = this.list[key];
if (!fns || fns.length === 0) {
return false;
}
fns.forEach(fn => {
fn.apply(this, arguments);
});
},
remove(key, fn) {
// 这回我们加入了取消订阅的方法
let fns = this.list[key];
// 如果缓存列表中没有函数,返回false
if (!fns) return false;
// 如果没有传对应函数的话
// 就会将key值对应缓存列表中的函数都清空掉
if (!fn) {
fns && (fns.length = 0);
} else {
// 遍历缓存列表,看看传入的fn与哪个函数相同
// 如果相同就直接从缓存列表中删掉即可
fns.forEach((cb, i) => {
if (cb === fn) {
fns.splice(i, 1);
}
});
}
}
};
function xiaoming(data) {
console.log('xiao明收到消息');
console.log(data);
}
function xiaohong(data) {
console.log('xiao红收到消息');
console.log(data);
}
// 订阅消息
event.on('pet', xiaoming);
event.on('pet', xiaohong);
// 小红取消订阅
event.remove('pet', xiaohong);
// 发布
event.emit('pet', ['哈哈', '呵呵']);
xiao明收到消息
["哈哈", "呵呵"]
面向对象的写一遍
/**
* 发布订阅模式(观察者模式)
* handles: 事件处理函数集合
* on: 订阅事件
* emit: 发布事件
* off: 删除事件
**/
class PubSub {
constructor() {
this.handles = {};
}
// 订阅事件
on(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
this.handles[eventType] = [];
}
if (typeof handle == 'function') {
this.handles[eventType].push(handle);
} else {
throw new Error('缺少回调函数');
}
return this;
}
// 发布事件
emit(eventType, ...args) {
if (this.handles.hasOwnProperty(eventType)) {
this.handles[eventType].forEach((item, key, arr) => {
item.apply(null, args);
})
} else {
throw new Error(`"${eventType}"事件未注册`);
}
return this;
}
// 删除事件
off(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
throw new Error(`"${eventType}"事件未注册`);
} else if (typeof handle != 'function') {
throw new Error('缺少回调函数');
} else {
this.handles[eventType].forEach((item, key, arr) => {
if (item == handle) {
arr.splice(key, 1);
}
})
}
return this; // 实现链式操作
}
}
// 下面做一些骚操作
let callback = function () {
console.log('you are so nice');
}
let pubsub = new PubSub();
pubsub.on('completed', (...args) => {
console.log(args.join(' '));
}).on('completed', callback);
pubsub.emit('completed', 'what', 'a', 'fucking day');
pubsub.off('completed', callback);
pubsub.emit('completed', 'fucking', 'again');
//输出
what a fucking day
you are so nice
fucking again