发布订阅模式
比如:pubsub-j
含义说明
发布一订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知 先订阅再发布
作用
- 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。
- 可以应用在异步编程中,替代回调函数,可以订阅ajax之后的事件,只需要订阅自己需要的部分 (那么ajax掉用发布之后订间的就可以拿到消息了) (不需要关心对象在异步运行时候的状态)
- 对象之间的松耦合,两个对象之间都互相不了解彼此;但是,不影响通信,当有新的订阅者出现的时候,发布的代码无需要改变同样发布的代码改变,只要之前约定的事件的名称没有改变,也不影响订阅。
- vue react之间实现跨组件之间的传值
缺点
- 创建订阅者需要消耗一定的时间和内存。
- 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。
生活中的实例
比如小红最近在淘宝网上看上一双鞋子,但是呢联系到卖家后,才发现这双鞋卖光了,但是小红对这双鞋又非常喜欢,所以呢联系卖家,问卖家什么时候有货,卖家告诉她, 要等一个星期后才有货,卖家告诉小红,要是你喜欢的话,你可以收藏我们的店铺,等有货的时候再通知你,所以小红收藏了此店铺,但与此同时,小明,小花等也喜欢这双鞋,也收藏了该店铺;等来货的时候就依次会通知他们;
如何实现发布--订阅模式?
- 首先要想好谁是发布者(比如上面的卖家)。
- 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如上面的买家收藏了卖家的店铺,卖家通过收藏了该店铺的个列表名单)。
- 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。
// 定义发布者
let shoeObj = {};
// 有一个列表用来存放函数
shoeObj.list = [];
// 增加订阅者
shoeObj.listen = function(key, fn){
// 判断是否存在
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
}
// 发布消息
shoeObj.trigger = function(){
// 取出key
let key = Array.prototype.shift.call(arguments);
let fns = this.list[key];
// 遍历这个数据, 然后执行这个函数
// for(let i = 0, fn;fn = this.list[i++];) {
// fn();
// }
if (!fns || fns.length == 0) {
return;
}
for(let i = 0;i < fns.length; i++) {
let fn = fns[i];
fn.apply(this, arguments);
}
}
// 运用
// 订阅
shoeObj.listen('red', function(color, size) {
console.log('======1=======')
console.log(`颜色是${color}`);
console.log(`大小是${size}`);
});
shoeObj.listen('blue', function(color, size) {
console.log('======2=======')
console.log(`颜色是${color}`);
console.log(`大小是${size}`);
});
console.log(shoeObj, '==shoeObj=')
// 发布
shoeObj.trigger('red', '红色', 32);
shoeObj.trigger('blue', '蓝色', 20);
总结:
const pubsub = (function() {
let list = {}, listen, trigger, remove;
listen = function(key, fn) {
if (!list[key]) {
list[key] = [];
}
list[key].push(fn);
}
trigger = function(){
let key = Array.prototype.shift.call(arguments);
let fns = list[key];
if (!fns || fns.length === 0) {
return false;
}
for(let i = 0; i < fns.length; i++) {
let fn = fns[i];
fn.apply(this, arguments);
}
}
remove = function(key, fn) {
let fns = list[key];
if (!fns) {
fns && (fns.length = 0);
return false;
}
for(let i = 0; i < fns.length; i++) {
let _fn = fns[i];
if (_fn === fn) {
fns.splice(i, 1);
}
}
}
return {
listen,
trigger,
remove,
};
})();
实例1:
<button class="count">点击我</button>
<div class="show-count"></div>
// 发布
function publish(){
let count = 0;
let button = document.querySelector('.count');
button.addEventListener('click', function(e) {
pubsub.trigger('add', count++)
});
}
// 订阅
function subscribe(){
let showCount = document.querySelector('.show-count');
pubsub.listen('add', function(count) {
showCount.textContent = count;
});
}
function init(){
publish();
subscribe();
}
init();
改进异步操作中的强耦合
业务场景
假如正在开发一个商城网站,网站里有header头部、 nav导航、消息列表、购物车等模块。这几个模块的染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。这是很正常的,比如用户的名字和头像要显示在header模块里,而这两个字段都来自用户登录后返回的信息
login.succ(function(data){
//设置header模块的头像
header.setAvatar(data.avatar);
//设置导航模块的头像
nav.setAvatar( data.avatar );
// 刷新消息列表
message.refresh();
//刷新购物车列表
cart.refresh();
})
强耦合
现在必须了解header模块里设置头像的方法叫setAvatar、购物车模块里刷新的方法叫refresh,这种耦合性会使程序变得僵硬,header模块不能随意再改变setAvatar的方法名,它自身的名字也不能被改为header1、header2。
- 等到有一天,项目中又新增了一个收货地址管理的模块,在最后部分加上这行代码
login.succ(function(data){
// ...
address.refresh();
})
发布订阅模式,实现低耦合
用发布-订阅模式重写之后,对用户信息感兴趣的业务模块将自行订阅登录成功的消息事件。当登录成功时,登录模块只需要发布登录成功的消息,而业务方接受到消息之后,就会开始进行各自的业务处理,登录模块并不关心业务方究竟要做什么,也不想去了解它们的内部细节。
$.ajax(' http://xx.com?login', function(data) {
//登录成功消息
//发布登录成功的
login.trigger('loginSucc' ,data);
});
- 各模块监听登录成功的消息
// header模块
let header = (function(){
login.listen('loginSucc', function(){
//设置header模块的头像
header.setAvatar(data.avatar);
});
return {
setAvatar: function(){
console.log('设置header模块的头像');
},
};
})();