首頁 >web前端 >js教程 >js發布訂閱者模式詳細的個人理解

js發布訂閱者模式詳細的個人理解

大家讲道理
大家讲道理原創
2017-04-11 10:39:401736瀏覽

先摘抄一下javascript設計模式與開發實踐一書中的文字介紹//現實中的發布訂閱模式   
小明最近看上了一套房子,到了售樓處之後才被告知,該樓盤的房子早已售罄。還好售樓MM告訴小明,不久後還有一些尾盤推出,開發商正在辦理相關手續,手續辦好後便可以購買。 但到底是什麼時候,目前還沒有人能夠知道。於是小明記下了售樓處的電話,以後每天都會打電話過去詢問是不是已經到了購買時間。除了小明,還有小紅、小強、小龍也會每天向售樓諮詢這個問題。一個星期過後,售MM 決 定辭職,因為厭倦了每天回答1000 個相同內容的電話。

當然現實中沒有這麼笨的銷售公司,實際上故事是這樣的:小明離開之前,把電話號碼留在了售樓處。售樓MM 答應他,新樓盤一推出就馬上發訊息通知小明。小紅、小強和小龍也是一樣,他們的電話號都被記在售樓處的花名冊上,新樓盤推出的時候,售樓MM會翻開花名冊,遍歷上面的電話號碼,依次發送一條短信來通知他們。在剛剛的例子中,發送簡訊通知就是一個典型的發布—訂閱模式,小明、小紅等購買者都是訂閱者,他們訂閱了房子開售的消息。售樓處作為發布者,會在適當的時候遍歷花名冊上的電話 號碼,依序給購屋者發布訊息。    

可以發現,在這個範例中使用發布—訂閱模式有著顯而易見的優點。購屋者不用再天天打電話給售樓處諮詢開售時間,在適當的時間點,售樓處作為發布者會通知這些消息訂閱者。購屋者和售樓處之間不再強耦合在一起,當有新的購屋者出現時,他只需把手機號碼留在售樓處,售樓處不關心購房者的任何情況,不管購房者是男是女還是一隻猴子。 而售 樓處的任何變動也不會影響購買者,比如售樓MM 離職,售樓處從一樓搬到二樓,這些 改變都跟購房者無關,只要售樓處記得發短信這件事情。

首先要指定好誰充當發布者(例如售樓處);然後給發布者添加一個緩存列表,用於存放回調函數以便通知訂閱者(售樓處的花名冊); 最後發布訊息的時候,發布者會遍歷這個快取列表,依序觸發裡面存放的訂閱者回呼函數(遍歷花名冊,挨個發送簡訊)。另外,我們也可以往回呼函數裡填入一些參數,訂閱者可以接收這些參數。這是很有必要的,例如售樓處可以在發給訂閱者的短信裡加上房子的單價、面積、容積率等信息,訂閱者接收到這些信息之後可以進行各自的處理:

  • <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>观察者模式</title>
    </head>
    <body>
    <button id="count">点我</button>
    <p id="show"></p>
    <script>
        //我的个人理解
        //发布者首先需要一个对象来保存事件的回调函数,这个对象的属性就是事件类型,比如{"click":[fn1,fn2],"mouseover":[fn3,fn4]}
        //订阅事件:首先要判断这个事件类型是否存在当前的对象上,如果不存在那就初始化一下就是添加这个属性,并且给这个属性的值设置为空数组
        //如果已经存在了那么久开始把该对调函数push进去对应的属性的值, 该值为一个数组
        //发布事件:发布事件就要一个个的通知订阅者,还有就是你发布了什么事件。这里要有if判断,
        // 先从参数中取出要发布事件的key,然后获取到key对应的回调函数数组,取出数组后先判断数组是否存在或者数组长度是否为0,如果不存在或者为0
        //就结束程序,若上述条件不成立就执行for循环,一次遍历回调函数数组,并执行里面的函数,这里需要改变回调函数的this指向。防止丢之
        //this。
        //移出事件:先判断要移出的key的值是否为空,若空则退出程序否则再判断是否传入了要移出哪个回调函数,若空,则认为要移出全部的函数,就把数组长度设置为0
        //
      //下面是js设计模式与开发实践上的实现过程
        var Event=(function(){
                var clientList={},
                    listen,
                    trigger,
                    remove;
            listen=function(key,fn){
                if(!clientList[key]){
                    clientList[key]=[];
                }
                clientList[key].push(fn);
            };
            trigger=function(){
                var key=Array.prototype.shift.call(arguments),
                    cb=clientList[key];
                if(!cb||cb.length==0){
                    return false;//如果没有保存回调函数就退出程序
                }
                for(var i=0;i<cb.length;i++ ){
                    cb[i].apply(this,arguments);//为什么要用applly呢,这是为了把触发时传入的参数传入给cb[i]对应的回调函数里面使用并且还要改变this指向
                }
            };
            remove=function (key,fn) {
    
                var cb=clientList[key];
                if(!cb){
                    return false;//如果要移除的key的值为空就代表没有人订阅该key和设置回调函数则直接返回
                }
                if(!fn){//如果没有传入具体的回调函数,表示需要取消key对应的消息的所有订阅
                    cb&&(cb.length=0);//这里cb是已经存在了就把cb的length设置为0.我有疑问,到这里其实cb不会不存在了吧上面如果不存在就退出了如何会走到这一步?
                }
                else{
                    for(var l=0;l<cb.length;l++){
                        var _cb=cb[l];
                        if(_cb===fn){//寻找哪个和要删除的回调函数一样
                            cb.splice(l,1);//从l处删除一个元素
                        }
                    }
                }
            };
            return {
                listen:listen,
                trigger:trigger,
                remove:remove
            }//这里返回这个对象其实是相当于中介代理的意思,请看下面的实际例子解释:
    //我们给每个发布者对象都添加了listen 和trigger 方法,以及一个缓存列表clientList,
    //这其实是一种资源浪费。
    //小明跟售楼处对象还是存在一定的耦合性,小明至少要知道售楼处对象的名字是
    //才能顺利的订阅到事件。
    //因此返回一个通用的对象,不必知道这个对象的名字,只需要把这个对象的一个引用给一个变量就行了
        })();
        Event.listen("A",fn1=function(data){
            console.log(data);
        })
        Event.listen("A",fn2=function(data){
            console.log(data);
        })
        Event.remove("A",fn1);
        Event.trigger("A","猴赛雷啊");
    </script>
    <script>
        var a=(function(){
            var count=0;
            var btn=document.getElementById(&#39;count&#39;);
            btn.onclick=function () {
                Event.trigger(&#39;add&#39;,count++);
            }
        })();
        var b=(function () {
            var p=document.getElementById(&#39;show&#39;);
            Event.listen(&#39;add&#39;,function(data){
                p.innerHTML=data;
            })
        })();
    </script>
    </body>
    </html>
最後作者也交代了發布訂閱模式的不利之處:
模組之間如果用了太多的全局發布—訂閱模式來通信,那麼模組與模組之間的聯繫就被隱藏到了背後。我們最終會搞不清楚訊息來自哪個模組,或者訊息會流向哪些模組,這又會給我們的維護帶來一些麻煩,也許某個模組的作用就是暴露一些介面給其他模組調用。

#

以上是js發布訂閱者模式詳細的個人理解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn