javascript設計模式之觀察者模式

這篇筆記主要記錄學習思路及收穫,分享出來拋磚引玉,如有謬誤或優化空間,歡迎交流。

要理解觀察者模式,可以類比vue中的EventBus,其實就是一個全局的觀察者對象($bus),上面有註冊事件($bus.on())和發送事件($bus.emit())的方法,當然因爲需要會註冊很多事件,所以內部還有一個事件列表屬性_events來存儲註冊的事件。下面爲學習筆記,對觀察者模式做簡單實現。

基於上面的思路,首先要有一個對象,它有一個私有的列表屬性和對外暴露的兩個方法

let Observer = (()=>{
    _events:[];
    return {
        retister:()=>{},
        issue:()=>{},
    }
})()

接下來一步步實現。

首先,觀察者對象內部要有一個存儲事件的列表屬性

let Observer = (()=>{
    // 防止事件隊列暴露而被篡改故將事件容器作爲靜態私有變量保存
    let _events = [];
})()

其次是註冊事件的register方法,需要兩個參數:需要觀察的事件名稱type和這個事件被觸發後具體的執行內容fn

let Observer = (()=>{
    // 防止事件隊列暴露而被篡改故將事件容器作爲靜態私有變量保存
    let _events = {};
    return {
        register: (type,fn)=>{
            //如果此消息類型不存在則應該創建一個(判斷對象上是否有某個屬性)
            // if (typeof(_events[type])==='undefined'){
            if (!(type in _events)){
                _events[type] = [fn];
            } else {//如果此消息類型已存在,則直接將對應動作推入消息隊列
                _events[type].push(fn);
            }
        },
    }
})()

再然後就是觸發事件的issue方法,也需要兩個參數:要觸發的事件type及傳遞過去的參數arg

let Observer = (()=>{
    // 防止事件隊列暴露而被篡改故將事件容器作爲靜態私有變量保存
    let _events = {};
    return {
        register: (type,fn)=>{
            //如果此消息類型不存在則應該創建一個(判斷對象上是否有某個屬性)
            // if (typeof(_events[type])==='undefined'){
            if (!(type in _events)){
                _events[type] = [fn];
            } else {//如果此消息類型已存在,則直接將對應動作推入消息隊列
                _events[type].push(fn);
            }
        },
        issue: (type,arg)=>{
            // 如果沒有此類型,返回
            // if (typeof (_events[type]) === 'undefined'){
            if (!(type in _events)) {
                return;
            }
            for (let i=0;i<_events[type].length;i++){
                _events[type][i](arg);
            }
        },
    }
})()

最後,可以再加一個可以移除已監聽事件的remove方法

let Observer = (()=>{
    // 防止事件隊列暴露而被篡改故將事件容器作爲靜態私有變量保存
    let _events = {};
    return {
        register: (type,fn)=>{
            //如果此消息類型不存在則應該創建一個(判斷對象上是否有某個屬性)
            // if (typeof(_events[type])==='undefined'){
            if (!(type in _events)){
                _events[type] = [fn];
            } else {//如果此消息類型已存在,則直接將對應動作推入消息隊列
                _events[type].push(fn);
            }
        },
        issue: (type,arg)=>{
            // 如果沒有此類型,返回
            // if (typeof (_events[type]) === 'undefined'){
                if (!(type in _events)) {
                return;
            }
            for (let i=0;i<_events[type].length;i++){
                _events[type][i](arg);
            }
        },
        // 此方法可以類比document.removerEventListener(evt,fn)
        remove: (type,fn)=>{
            // 如果這個類型的事件存在
            if (_events[type] instanceof Array){
                for (let i = 0; i < _events[type].length; i++) {
                    _events[type][i] === fn && _events[type].splice(i,1);
                }
            }
        },
        // 因爲外部獲取不到私有屬性`_events`,所以臨時用這個方法檢查`remove`方法是否生效
        check (){
            console.log('_events事件隊列',_events);
        }
    }
})()

至此一個基本的觀察者對象就寫出來了,接下來跑一下看效果。

// 註冊的事件被觸發後需要執行的動作
function handler (val){
    console.log('val:',val*2);
}

// 註冊事件及對應的執行動作
Observer.register('eventName',handler);

// 觸發事件
function test (){
    Observer.issue('eventName',5); //10
    // 對比執行remove事件前後的事件列表內容
    Observer.check();
    Observer.remove('eventName',fn);
    observer.check();
}

觀察者模式在解決類的耦合中的應用小例子。想象一下課堂上老師提問,學生回答問題的場景。老師提問的問題名稱相當於發出的事件,如果學生聽到這個問題(事先註冊了這個問題名稱),那麼便會回答(響應事件的動作)。

學生類:

let Student = function (result) {
    // 問題答案
    this.result = result;
    // 回答的動作
    this.answer = ()=>{
        console.log('答案',this.result);
    }   
}
Student.prototype.listen = function(question){
    Observer.register(question,this.answer);
}
Student.prototype.sleep = function (question) {
    Observer.remove(question,this.answer);
    console.log('這個問題我聽不到聽不到~');
}

教師類:

let Teacher = function (){}
// 對某一個問題發出問題
Teacher.prototype.ask = function (question) {
    Observer.issue(question);
}

執行:

let student1 = new Student('學生1回答問題');
let student2 = new Student('學生2回答問題');
let student3 = new Student('學生3:呼嚕呼嚕~');
student1.listen('雞生蛋還是蛋生雞?');
student2.listen('雞生蛋還是蛋生雞?');
student3.listen('雞生蛋還是蛋生雞?');
let teacher = new Teacher();
// 老師發問
function test () {
    teacher.ask('雞生蛋還是蛋生雞?');
}
//答案 學生1回答問題
//es5觀察者.html:68 答案 學生2回答問題
//es5觀察者.html:68 答案 學生3:呼嚕呼嚕~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章