用觀察者模式編寫一個可被其他物件擴充複用自定義事件系統

混沌傳奇發表於2018-02-24

觀察者模式

定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知

什麼是觀察者模式?

釋出—訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知。在 JavaScript 開發中,我們一般用事件模型來替代傳統的釋出—訂閱模式。

舉個例子:

賣水果的張老闆和王老闆都要進一批香蕉,他們的水果都是在一個叫錢多多(下面統稱為錢老闆)的水果批發商那裡進的。當張老闆和王老闆到錢老闆那裡進水果的時候,錢老闆告訴張老闆和王老闆,香蕉還沒有到貨,得過幾天才到貨。無奈之下,張老闆和王老闆都把他們的電話號碼留在了錢老闆那裡,囑咐錢老闆,香蕉到貨後,第一時間通知他們。

上面錢老闆就扮演了釋出者的角色,張老闆和王老闆則扮演的是訂閱者角色。在香蕉到貨後,錢老闆會主動給張老闆和王老發訊息,讓兩位老闆來取香蕉。這樣的好處就是:香蕉沒到的這段時間,張老闆和王老闆可以做其他的事情,不用主動聯絡錢老闆,只需等待錢老闆的訊息即可。也就是程式程式碼中的時間上解耦,物件間解耦。

自定義事件

其實觀察者模式我們都曾使用過,就是我們熟悉的事件
但是內建的事件很多時候不能滿足我們的要求
所以我們需要自定義事件


現在我們想實現這樣的功能 定義一個事件物件,它有以下功能

  • 監聽事件(訂閱事件)
  • 觸發事件(事件釋出)
  • 移除事件(取消訂閱事件)

當然我們不可能只訂閱一個事件,可能會有很多
所以我們要針對不同的事件設定不同的”鍵”
這樣我們儲存事件的結構應該是這樣的

EventList = {
    evtName1: [回撥函式1,回撥函式2,...],
    evtName2: [回撥函式1,回撥函式2,...],
    evtName3: [回撥函式1,回撥函式2,...],
}
複製程式碼

程式碼如下

var createEventSys = function(){
    return {
        // 通過on介面監聽事件eventName
        // 如果事件eventName被觸發,則執行callback回撥函式
        on: function (eventName, callback) {
            //如果Event物件沒有handles屬性,則給Event物件定義屬性handles,初始值為{}
            //handles屬性是用來儲存事件和回撥執行函式的(即儲存訂閱的事件和觸發事件後執行的相應函式方法)
            if(!this.handles){
                this.handles={};
            }
            //如果handles中不存在事件eventName,則將事件儲存在handles中,同時初始化該事件對應的回撥邏輯函式集合
            if(!this.handles[eventName]){
                this.handles[eventName]=[];
            }
            //往handles中的eventName對應的回撥邏輯函式集合push回撥函式callback
            this.handles[eventName].push(callback);
        },
        // 觸發事件 eventName
        emit: function (eventName) {
            //如果事件eventName有訂閱者,則依次執行事件eventName的訂閱者相應的回撥方法
           if(this.handles[arguments[0]]){
               for(var i=0;i<this.handles[arguments[0]].length;i++){
                   this.handles[arguments[0]][i](arguments[1]);
               }
           }
        },
        //移除事件 eventName
        remove: function (eventName, fn) {
            //判斷事件eventName是否存在fn這個觀察者,如果有,則移除事件eventName的fn觀察者
            if(this.handles[eventName]){
                for(var i=0; i<this.handles[eventName].length; i++){
                    if(this.handles[eventName][i] === fn){
                        this.handles[eventName].splice(i,1);
                        break;
                    }
                }
            }
        }
    };
}
var Event = createEventSys();
Event.on('test', function (result) {
    console.log(result);
});
Event.on('test', function () {
    console.log('test');
});
Event.emit('test', 'hello world'); // 輸出 'hello world' 和 'test'

//物件person1和物件person2擴充複用自定義系統
var person1 = {};
var person2 = {};
Object.assign(person1, createEventSys());
Object.assign(person2, createEventSys());
person1.on('call1', function () {
    console.log('person1');
});
person2.on('call2', function () {
    console.log('person2');
});
person1.emit('call1'); // 輸出 'person1'
person1.emit('call2'); // 沒有輸出
person2.emit('call1'); // 沒有輸出
person2.emit('call2'); // 輸出 'person2'
複製程式碼

如上面程式碼這樣,我們用觀察者模式就實現了一個基本完善的自定義事件系統。

總結

觀察者模式有兩個明顯的優點

時間上解耦
物件間解耦

在前端開發中,很多地方都適合用觀察者模式來做,在適當的地方善用觀察者模式

希望這篇文章對大家有幫助,喜歡的話,請關注我,我會持續更新一些技術文章到我的掘金主頁,謝謝大家支援!

相關文章