前言
之前在我们项目中有一些方法经常使用,但是我本以为那些方法是老大封装的mvvm框架中本来就存在的监听方法,后来时间富裕的时候,我看了看很多项目的老代码,原来我之前一直使用的this.watch方法和this.publish方法是每个实例对象的基类中定义的方法。然后我找到了该基类,原来基类也是继承了一个叫做PubSub的类,之后我看了PubSub这个不足100行代码的类,它只有三个方法subscribe,unsubscribe,publish哦!原来这就是我上学时期学习过的发布-订阅设计模式,但是那时候只是看了看书,知道和了解了当时书上的案例和讲解,然而从没有把它手动应用到过项目中,所以就导致了自己用的方法都不知其原理和不知其然,赶紧好好反复的跟了一下项目的代码和方法的执行,又到网上搜了相关的文档,现在才算是知其然吧,忽然发现写业务写久了就容易不去思考,不去追求为什么,也以此来警示一下自己吧,对待任何事情都应该用学者的心态去对待。
言归正传说一说这个发布-订阅设计模式
公众号大家都关注过吧,你订阅了某个平台或者某人的公众号,当他推送消息后,你就可以阅读他的新文章,而且谁都可以关注这个公众号主体。这一过程其实就是一个发布-订阅设计模式的现实场景,它是一种一对多的形式,公众号的主体就是我们的发布者,所有的读者就是订阅者,而且我们随时可以取消关注这个公众号,所以也会提供相应的取消订阅方法。
一步步梳理思路理解和实现
-
作为发布者应该提供什么方法给订阅者(提供subscribe)
-
发布者提供了订阅的方法后应该将这些订阅者都存起来,记录以便日后给他们推送消息(list用来存储)
-
作为发布者要有推送消息的方法(publish方法)
-
订阅者如何订阅消息或者事件,订阅者还可以取消订阅
-
代码实现
//定义一个发布者对象
var pubsubObj = {};
pubsubObj.list = {}; // 此处修改一下因为会有不同类型的订阅 用于存储订阅者的回调函数
//给订阅者提供订阅的方法
pubsubObj.subscribe = function (key,callbak) {
if (!this.list[key]) {
//如果没有订阅此事件,给此事件创建一个列表
this.list[key] = [];
}
this.list[key].push(callbak);
}
//发布消息的功能
pubsubObj.publish = function (key,args) {
var fns = this.list[key]; // 取出该消息对应的回调函数的集合
// 如果没有订阅过该消息的话,则返回
if(!fns || fns.length === 0) {
return;
}
var len = fns.length;
//执行对应的订阅函数集合
while (len--) {
fns[len].apply(this,args);
}
}
//取消订阅的功能
pubsubObj.unsubscribe = function(key,fn){
var fns = this.list[key];
if(!fns) {
return false;
}
//如果没有传回调则证明取消key事件的所有订阅
if (!fn) {
fns && (fns.length = 0);
}
else {
//将和参数fn相同的回调删除
var len = fns.length;
while(len--){
if(fns[len] === fn){
fns.splice(len, 1);
}
}
}
};
优化和封装写出好的代码
var pubsubClass = {
list: {},
subscribe:function (key,callbak) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(callbak);
},
publish: function (key,args) {
var fns = this.list[key];
if(!fns || fns.length === 0) {
return;
}
var len = fns.length;
while (len--) {
fns[len].apply(this,args);
}
},
unsubscribe:function(key,fn){
var fns = this.list[key];
if(!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
}
else {
//将和参数fn相同的回调删除
var len = fns.length;
while(len--){
if(fns[len] === fn){
fns.splice(len, 1);
}
}
}
}
};
看完了以上的代码,我们会发现只定义一个pubsubObj对象,这些功能只能用在这一个对象上,而不能复用,所以封装好之后将它封装成一个对象,可以让其他对象来使用它的功能,也可以根据你们项目的框架将它封装为一个类等做法来进行不同的实现
讲讲实际案例
在我们的项目中,定义了一个基类PubSub类,在需要用到发布订阅模式的功能时,即可给当前的对象继承该类的功能来使用PubSub类的方法。
实际场景:电商平台当用户购买一件商品时,购物车就会收到相关的产品信息,购物车组件的参数和属性需要及时变更,这个场景就非常适合使用PubSub类
代码片段讲解:
当用户在页面中购买一本书的时候,会给购物车做状态等一些变更
//购买一件商品
addBook:function(book){
//变更一下购物车的状态信息等
this.refreshState();
return this.saveAddBook(book,function() {
//调用接口把书加入购物车逻辑
}.bind(this));
return true;
},
refreshState: function(){
this.selectedBookCount = this.getSelectedBookCount();
this.totalCount = this.getTotalCount();
this.publish('change', [this]); //执行通知
},
//将订阅方法在这个基类中定义为watch监听方法
watch: function(eventName, callback) {
return this.subscribe(''+eventName, callback);
},
购物车对象
init:function(){
this.shopcart.watch('change', this.cartChanged.bind(this)),
},
cartChanged: function(cart) {
//当前相关逻辑操作
},
总结
通过文字的描述还有实际代码的讲解,应该对javascript中发布-订阅设计模式有了基本的了解和学习。最后的代码示例和实际场景是我自己编造的demo,但是非常适合真实的开发环境中,学是一个获取新知的过程,习则是将学习的内容内化的过程,以后在写代码和写业务逻辑的时候要给自己创造学习的机会,而不仅仅是代码能跑,逻辑能通就可以。
Cayley 一个不断努力学习的女程序员