携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情
前言
作为一个前端切图仔,少有和各类设计模式打交道。但这不影响我们学习设计模式的思维,来提升我们的代码水平。
本章通过讲解发布—订阅模式,希望能够让你对设计模式更一步的学习。
本章学习内容👇
- 完成一个完整的发布—订阅模式
- 总结发布—订阅模式
完成一个完整的发布—订阅模式
在上一章中,我们实现了一个简易的发布—订阅模式。
参考:《JavaScript发布-订阅模式与开发实践(中)》
在这一章节中,我们着手于完善一个完整的**发布—订阅模式**。
以下根据功能,我们逐步实现。首先👇
- 实现订阅到具体属性值
我们修改缓存池的结构,并在发布者实例化时赋予属性。在依赖注入时,通过键名缓存回调函数👇
class Publisher{
constructor(obj) {
this.obj = obj
this.cache = {}
}
dependent(key,callback){
if (!this.cache[key]){ //如果该属性还没有被订阅
this.cache[key] = []
}
this.cache[key].push(callback)
}
}
接着👇
- 实现,通知
订阅者时,只通知与属性改变相关的订阅者
在触发器通知订阅者时,检查具体哪个属性发生改变,只调用该属性缓存池中的回调函数。
class Publisher{
constructor(obj) {
this.obj = obj
this.cache = {}
}
dependent(key,callback){
if (!this.cache[key]){ //如果该属性还没有被订阅
this.cache[key] = []
}
this.cache[key].push(callback)
}
trigger(key){
if(this.cache[key]){
this.cache[key].forEach((callback)=>{
callback(key,this.obj[key])
})
}
}
}
我们通过一段测试代码来测试下这个功能👇
let publisher = new Publisher({ age:15, money:1 })
let subscriber1 = {
name:'one',
}
let subscriber2 = {
name:'two'
}
publisher.dependent('age',(key,value)=>{console.log(`${subscriber1.name} listen key:${key}, value change to ${value}`)})
publisher.dependent('money',(key,value)=>{console.log(`${subscriber2.name} listen key:${key}, value change to ${value}`)})
publisher.trigger('age')
publisher.trigger('money')
控制台输出
one listen key:age, value change to 15
two listen key:money, value change to 1
看来是完成了这个功能🤩
但是还存在一个问题,看看以下这段代码。
window.addEventListener('resize', ()=>{ console.log('size change')});
window.removeEventListener('resize', ()=>{ console.log('size change')});
我们在能够订阅属性的同时,同样也要赋予能够取消订阅属性的能力
因此👇
- 实现,
订阅者能够取消订阅的功能
class Publisher{
constructor(obj) {
this.obj = obj
this.cache = {}
}
dependent(key,callback){
if (!this.cache[key]){ //如果该属性还没有被订阅
this.cache[key] = []
}
this.cache[key].push(callback)
}
trigger(key){
if(this.cache[key]){
this.cache[key].forEach((callback)=>{
callback(key,this.obj[key])
})
}
}
unsubscribe( key , fn){
if (!this.cache[key]){
return false
}
if (!fn){
this.cache[key] = []
console.log(`已清除属性值:${key}的所有通知`)
}else{
this.cache[key].forEach((callback,index)=>{
if ( callback.toString() === fn.toString()){
this.cache[key].splice(index,1)
console.log(`已清除属性值:${key}的一个通知`)
}
})
}
}
}
同样的,我们通过一段测试代码来测试下这个功能👇
let publisher = new Publisher({ age:15, money:1 })
let subscriber1 = {
name:'one',
}
publisher.dependent('age',(key,value)=>{console.log(`${subscriber1.name} listen key:${key}, value change to ${value}`)})
let subscriber2 = {
name:'two'
}
publisher.dependent('age',(key,value)=>{console.log(`${subscriber2.name} listen key:${key}, value change to ${value}`)})
publisher.trigger('age')
publisher.unsubscribe('age',(key,value)=>{console.log(`${subscriber1.name} listen key:${key}, value change to ${value}`)})
publisher.unsubscribe('age')
控制台输出
one listen key:age, value change to 15
two listen key:age, value change to 15
已清除属性值:age的一个通知
已清除属性值:age的所有通知
这样我们就实现了一个完整的**发布—订阅模式**了🎉🎉🎉
总结
在这个章节中,我们逐步的学习了**发布—订阅模式**,并完成了从零的开发实践。
发布—订阅模式的优点相比大家都清楚了,那么缺点呢?
试想,我们放在回调池里的函数是不是会一直存在于我们的内存当中?即使,它从未触发。
并且,我们实现注入依赖这一步骤时也会消耗一定的时间和内存。
再者,如果过度使用/不正确依赖关系的话。可能会导致我们程序之间的逻辑难以维护。
因此,任何看起来便利的模式。我们在使用时,都应该三思而后行。
最后,阅读到这里的看官老爷,能不能赏口饭吃?三连可否?😏
\