ES5/ES6實(shí)現(xiàn)觀察者模式(自定義事件)

觀察者模式:觀察者模式(Observer mode)指的是函數(shù)自動(dòng)觀察數(shù)據(jù)對(duì)象,一旦對(duì)象有變化,函數(shù)就會(huì)自動(dòng)執(zhí)行。而js中最常見的觀察者模式就是事件觸發(fā)機(jī)制。

現(xiàn)在,讓我們重新實(shí)現(xiàn)一個(gè)自定義事件。

ES5實(shí)現(xiàn)

要實(shí)現(xiàn)一個(gè)自定義事件,首先要想清楚我們要干什么。

1.要有一個(gè)對(duì)象,存儲(chǔ)著它自己的觸發(fā)函數(shù),所以他的觸發(fā)函數(shù)應(yīng)該是這種類型

handler={
  type1:
  type2:
  ...
}

2.這個(gè)對(duì)象的觸發(fā)函數(shù)可能有很多種,比如一個(gè)onclick可能觸發(fā)多個(gè)事件,那么handler的屬性應(yīng)該是一個(gè)數(shù)組,每個(gè)數(shù)組的值都是一個(gè)函數(shù)

handler={
  type1:[func1,func2...],
  type2:[func2,func4...],
  ...
}

3.現(xiàn)在這個(gè)對(duì)象的主體部分已經(jīng)思考好了,現(xiàn)在就是要它‘動(dòng)起來’,給它添加各種動(dòng)作。
一個(gè)事件可能有哪些動(dòng)作呢?

  • add:添加事件某種類型的函數(shù),
  • remove: 移除某種類型的函數(shù),
  • fire:觸發(fā)某種類型的函數(shù),
  • once:觸發(fā)某種類型的函數(shù),然后移除掉這個(gè)函數(shù),

那么現(xiàn)在我們的自定義事件的骨架已經(jīng)搭建好了。

eventOb={
//函數(shù)儲(chǔ)存
handler:{
  type1:[func1,func2...],
  type2:[func2,func4...],
  ...
},

//主要事件
add:function(){},
remove:function(){},
fire:function(){},
once:function(){},

}

下面開始填充這些函數(shù),

1.add

添加一個(gè)事件監(jiān)聽,首先傳入?yún)?shù)應(yīng)該是 事件類型type,和觸發(fā)函數(shù) func,傳入的時(shí)候檢測有沒有這個(gè)函數(shù),有了就不重復(fù)添加,那么代碼并不難,應(yīng)該是下面這樣。

    add:function (type,func) {
        //檢測type是否存在
        if(eventOb.handleFunc[type]){
            //檢測事件是否存在,不存在則添加
            if(eventOb.handleFunc[type].indexOf(func)===-1){
                eventOb.handleFunc[type].push(func);
            }
        }else{
            eventOb.handleFunc[type]=[func];
        }

    },

2.remove

remove也需要指明傳入類型和函數(shù),和add很相像,首先我們就寫成下面的樣子

    remove:function (type,func) {
            let target = eventOb.handleFunc[type];
            let index=target.indexOf(func);
            target.splice(index,1);
    },

看起來很簡單,但是remove有一個(gè)潛在的需求,就是如果你的事件不存在,它應(yīng)該會(huì)報(bào)錯(cuò)。而這里不會(huì)報(bào)錯(cuò),index在func不存在的時(shí)候是-1;這時(shí)候不能正確的刪除,也不會(huì)報(bào)錯(cuò)。
所以我們需要改進(jìn)一下。

    remove:function (type,func) {
        try{
            let target = eventOb.handleFunc[type];
            let index=target.indexOf(func);
            if(index===-1)throw error;
            target.splice(index,1);
        }catch (e){
            console.error('別老想搞什么飛機(jī),刪除我有的東西!');
        }

    },

當(dāng)index=-1拋出一個(gè)錯(cuò)誤,好了,解決的很簡陋,但是效果還行。

3.fire

觸發(fā)一個(gè)點(diǎn)擊事件肯定是要觸發(fā)它全部的函數(shù),這里也是一樣,所以只需要傳入type,然后事件可能不存在,像上面一樣處理。

    fire:function (type,func) {
        try{
            let target = eventOb.handleFunc[type];
                let count = target.length;
                for (var i = 0; i < count; i++) {
                //加()使立即執(zhí)行
                    target[i]();
                }
            
        }catch (e){
            console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
        }
    },

看起來很還好。但是。。。嗯。先說once。

4.once

once這里應(yīng)該相當(dāng)簡單。感覺起來是這樣。。。fire,然后remove?來試試

    once:function (type,func) {
            eventOb.fire(type);//不能觸發(fā)某個(gè)事件,應(yīng)該為fire(type,func)
            eventOb.remove(type, func);
    }

這就發(fā)現(xiàn)了問題,我只想觸發(fā)并且刪除某個(gè)事件怎么辦,fire一下就全觸發(fā)了呀。
所以fire的問題就顯現(xiàn)出來了。我們還是要給它一個(gè)func,但是可選。讓我們改寫一下fire

    fire:function (type,func) {
        try{
            let target = eventOb.handleFunc[type];
            if(arguments.length===1) {
           //不傳func則全部觸發(fā)
                let count = target.length;
                for (var i = 0; i < count; i++) {
                    target[i]();
                }
            }else{
           //傳func則觸發(fā)func
                let index=target.indexOf(func);
                if(index===-1)throw error;
                func();
            }
           //need some code
        }catch (e){
            console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
            //need some code
        }
    },

這樣once就寫好了?
試試,對(duì)一個(gè)不存在的事件觸發(fā)once,發(fā)現(xiàn)once會(huì)報(bào)兩個(gè)錯(cuò)誤,

once.png

這樣當(dāng)然不太好,想想,嗯可以給fire一個(gè)返回值,當(dāng)fire報(bào)錯(cuò)就return false不執(zhí)行remove

最后的once長這樣

    once:function (type,func) {
            eventOb.fire(type, func)?
            eventOb.remove(type, func):null;
    }

并且在fire上面 need code的地方補(bǔ)上return true 和return false,大功告成~

完整代碼:

var eventOb={

    handleFunc:{},
    add:function (type,func) {
        if(eventOb.handleFunc[type]){
            //檢測事件是否存在,不存在則添加
            if(eventOb.handleFunc[type].indexOf(func)===-1){
                eventOb.handleFunc[type].push(func);
            }
        }else{
            eventOb.handleFunc[type]=[func];
        }

    },
    
    fire:function (type,func) {
        try{
            let target = eventOb.handleFunc[type];
            if(arguments.length===1) {
                let count = target.length;
                for (var i = 0; i < count; i++) {
                    target[i]();
                }
            }else{
                let index=target.indexOf(func);
                if(index===-1)throw error;
                func();
            }
            return true;
        }catch (e){
            console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
            return false;
        }
    },

    remove:function (type,func) {
        try{
            let target = eventOb.handleFunc[type];
            let index=target.indexOf(func);
            if(index===-1)throw error;
            target.splice(index,1);
        }catch (e){
            console.error('別老想搞什么飛機(jī),刪除我有的東西!');
        }

    },

    once:function (type,func) {

            eventOb.fire(type, func)?
            eventOb.remove(type, func):null;

    }



};

ES6

ES6不會(huì)也這么一大堆吧。。。
首先還是說為什么要用ES6.。。。
上面的一個(gè)實(shí)例我們發(fā)現(xiàn)是不能繼承的,只有一個(gè)實(shí)例,我們要用ES5把它改造成一個(gè)可以繼承的函數(shù)的話(并且使用最佳的組合繼承模式),首先要把handleFunc掛載在this上,然后把方法掛載在eventOb的prototype上,使用es6則更顯而易見。

class eventObs {
    constructor(){
        this.handleFunc={}
    }

    add(type,func){
        if(this.handleFunc[type]){
            if(this.handleFunc[type].indexOf(func)===-1){
                this.handleFunc[type].push(func);
            }
        }else{
            this.handleFunc[type]=[func];
        }

    };

    fire(type,func){
        try{

            if(arguments.length===1) {
                let target = this.handleFunc[type];
                let count = target.length;
                for (var i = 0; i < count; i++) {
                    target[i]();
                }
            }else{
                let target = this.handleFunc[type];
                let index=target.indexOf(func);
                if(index===-1)throw error;
                func();
            }
            return true;
        }catch (e){
            console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
            return false;
        }
    };

    remove(type,func){
        try{
            let target = this.handleFunc[type];
            let index=target.indexOf(func);
            if(index===-1)throw error;
            target.splice(index,1);
        }catch (e){
            console.error('別老想搞什么飛機(jī),刪除我有的東西!');
        }

    };

    once(type,func) {

        this.fire(type, func)?
            this.remove(type, func):null;

    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容