前言
之前在我們專案中有一些方法經常使用,但是我本以為那些方法是老大封裝的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 一個不斷努力學習的女程式設計師