一. 认识发布订阅者模式
发布订阅者模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知。我们一般用事件模型来代替传统发布订阅模式。
就拿用户订阅公众号来说,用户根据需求来订阅自己喜欢的公众号,一旦公众号有新的文章,便会主动推给每个订阅它的用户,这种模式解决了很多日常的需求,就比如现在比较流行的框架 vue , 其关键的动态绑定原理就是由订阅发布者模式和数据劫持技术来实现,接下来我们来更深入了解。
二. 发布订阅模式的实现
实现思想:在一个全局变量对象中封装一个监听事件和一个发布事件,首先需要有一个用来保存事件和监听的对象,将订阅相同的事件保存在同一个数组中,发布某个订阅后,遍历数组中的函数,依次执行。
let Emitter = {};
Emitter.list = {}; // 用来缓存事件
// 监听事件
Emitter.on = function ( key, fn ) {
(this.list[ key ] || (this.list[ key ] = [])).push( fn );
};
// 发布事件
Emitter.emit = function (key, ...args) {
let list = this.list[key];
if(list.length == 0){
return
}
list.forEach(( fn )=>{
fn.apply(this, args);
})
};
// 取消订阅
Emitter.remove = function (key, fn) {
let fns = this.list[key];
if(fns.length == 0) {
return
}
if( !fn ) {
return this.list[key].length = 0;
}
for(let i = 0,l = fns.length; i < l ; i++){
if(fn == fns[i]) {
this.list[key].splice(i, 1);
}
}
};
let Listen1 = function( data ) {
console.log("我是订阅者1");
console.log("以下是文章内容:" +data );
}
let Listen2 = function(data) {
console.log("我是订阅者2")
}
Emitter.on('公众号1', Listen1);
Emitter.on('公众号1', Listen2);
Emitter.emit('公众号1','这是文章内容!');
Emitter.remove('公众号1', Listen1);
三. 实际开发应用
- 获取登录信息数据
首先我们模拟了 3 秒后获取到服务器传回的数据,虽然我们可以将所有渲染写入异步回调中,但是每次增加新的需求时,你必须找到原来的代码行修改,而使用订阅发布模式,我们只需将业务集中到新的需求上,让其监听 login 便可,很方便新增、管理或删除需求。
// 假定从服务器请求拿到用户登录信息数据,账号昵称,头像等等
(()=>{
setTimeout(()=>{
let data = {
nicname: "石头山",
photo: "菊花"
};
Emitter.emit('login', data);
},2000)
})();
let renderNicname = function( { nicname } ) { // 渲染
console.log("我的昵称是: ", nicname)
}
let renderPhoto = function( { photo } ) { // 渲染
console.log("我的头像: ", photo);
}
Emitter.on('login', renderNicname);
Emitter.on('login', renderPhoto);
- 模块间的通信
假如有一个需求,在模块 a 中点击按钮,在模块 b 中显示记录 a 中的点击次数。
// 模块 a
let count = 0;
let addA = function(count){
}
button.onclick = function() {
Emitter.emit('add', count++);
}
// 模块B
Emitter.on('add',( count )=> {
document.body.innerHTML = count;
})
四. 订阅发布模式的扩展
我们来想一个问题,我们平时所接触的订阅发布都是先订阅,然后再发布。那我们反着尝试一下,先发布,再订阅,毫无疑问是没有任何效果,但这在实际开发中又是一个必要的需求。比如QQ离线,你不在线时先把其他人给你发的消息保存下来,当你上线后,便可以接收到这些消息。
因此我们可以来实现一个离线缓冲空间,将未被订阅的发布函数存放在其中,当有新的订阅时,从新发布。
分析:我们重新修改代码,Emitter 中新增加了 cache 离线空间,用来存储先发布的事件,再 emit 执行时,我们新增了一个条件,判断是否存在该订阅,如果不存在将其保存到 cache 中,在 on 执行时,增加了一条分支,用来判断该订阅是否为先发布的订阅,如果是,则从新发布该订阅,并从 cache 中删除该条发布记录。
// 订阅事件
let Emitter = {};
Emitter.list = {}; // 用来缓存事件
Emitter.cache = {};
// 监听事件
Emitter.on = function ( key, fn ) {
(this.list[ key ] || (this.list[ key ] = [])).push( fn );
if(this.cache[ key ]) {
Emitter.emit(key, ...this.cache[ key ]); // 重新发布并且删除缓存
delete this.cache[key];
}
};
// 发布事件
Emitter.emit = function (key, ...args) {
let list = this.list[key];
if( !list ){ // 如果不存在该订阅,则先缓存
console.log("不存在该订阅先缓存");
this.cache[key] = args;
return;
}
if( list.length == 0 ){
return ;
}
list.forEach(( fn )=>{
fn.apply(this, args);
})
};
五. 总结
优点:一是时间上的解耦,二是对象之间的解耦。
缺点:创建订阅者本身消耗时间和内存,并且防止过度嵌套订阅,引起不必要的麻烦。
发布订阅模式是很重要也是常用的设计模式,可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。发布订阅让两个对象松耦合在一起,虽然不清楚彼此细节,但这不影响他们之间的通信。但是有时候使用回调而不是发布订阅模式显得更加的优雅,所以使用时我们要依情况而定。
本文介绍发布订阅模式的概念与实现,探讨其在获取登录信息、模块间通信等场景的应用,并提出模式扩展思路。
529

被折叠的 条评论
为什么被折叠?



