JavaScript之觀察者模式

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

故事背景
小明最近看上了一套房子,到了售樓處之後才被告知,該樓盤的房子早已售罄。好在售樓 MM 告訴小明,不久後還有一些尾盤推出,開發商正在辦理相關手續,手續辦好後便可以購買。 但到底是什麼時候,目前還沒有人能夠知道。
於是小明記下了售樓處的電話,以後每天都會打電話過去詢問是不是已經到了購買時間。除 了小明,還有小紅、小強、小龍也會每天向售樓處諮詢這個問題。一個星期過後,售樓 MM 決 定辭職,因爲厭倦了每天回答 1000 個相同內容的電話。
當然現實中沒有這麼笨的銷售公司,實際上故事是這樣的:小明離開之前,把電話號碼留在 了售樓處。售樓 MM 答應他,新樓盤一推出就馬上發信息通知小明。小紅、小強和小龍也是一 樣,他們的電話號碼都被記在售樓處的花名冊上,新樓盤推出的時候,售樓 MM 會翻開花名冊,遍歷上面的電話號碼,依次發送一條短信來通知他們。
發送短信通知就是一個典型的發佈—訂閱模式,小明、小紅等購買者都是 訂閱者,他們訂閱了房子開售的消息。售樓處作爲發佈者,會在合適的時候遍歷花名冊上的電話號碼,依次給購房者發佈消息。
代碼實現

先訂閱後發佈模式

var DEvent = (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),
        fns = clientList[key];
        if (!fns || fns.length === 0) {
            return false;
        }
        for (let index = 0; index < fns.length; index++) {
            const fn = fns[index];
            fn.apply(this, arguments);
        }
    };
    remove = function(key, fn) {
        var fns = clientList[key];
        if (!fns) {
            return false;
        }
        if (!fn) {
            fns && (fns.length = 0);
        } else {
            for (var l = fn.length - 1; l > 0 ; l--) {
                var _fn = fns[l];
                if (_fn === fn) {
                    fns.splice(l, 1);
                }
            }
        }
    };
    return {
        listen,
        trigger,
        remove
    };
})();
Event.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
     console.log( '價格= ' + price );  // 輸出:'價格=2000000'
});
Event.trigger( 'squareMeter88', 2000000 );// 售樓處發佈消息

應用場景

網站登錄
假如我們正在開發一個商城網站,網站裏有 header 頭部、nav 導航、消息列表、購物車等模塊。這幾個模塊的渲染有一個共同的前提條件,就是必須先用 ajax 異步請求獲取用戶的登錄信息。 這是很正常的,比如用戶的名字和頭像要顯示在 header 模塊裏,而這兩個字段都來自用戶登錄後 返回的信息。
至於 ajax 請求什麼時候能成功返回用戶信息,這點我們沒有辦法確定。現在的情節看起來像 極了售樓處的例子,小明不知道什麼時候開發商的售樓手續能夠成功辦下來。

$.ajax( 'http:// xxx.com?login', function(data){ // 登錄成功 
    login.trigger('loginSucc', data); // 發佈登錄成功的消息
});
var header = (function(){ // header 模塊 
    login.listen( 'loginSucc', function( data){
        header.setAvatar( data.avatar );
    }); 
    return {
        setAvatar: function( data ){
            console.log( '設置 header 模塊的頭像' );
        } 
    }
})();

var nav = (function(){
    login.listen( 'loginSucc', function( data ){// nav 模塊 
        nav.setAvatar( data.avatar );
    }); 
    return {
        setAvatar: function( avatar ){ 
            console.log( '設置 nav 模塊的頭像' );
        } 
    }
})();

先發布後訂閱模式(提供創建命名空間的功能)

var Event = (function(){
    var global = this, 
    Event,
    _default = 'default';
    
    Event = function(){
        var _listen,
        _trigger,
        _remove,
        _slice = Array.prototype.slice, 
        _shift = Array.prototype.shift, 
        _unshift = Array.prototype.unshift, 
        namespaceCache = {},
        _create,
        find,
        each = function( ary, fn ){
            var ret;
            for ( var i = 0, l = ary.length; i < l; i++ ){
                var n = ary[i];
                ret = fn.call( n, i, n); 
            }
            return ret; 
        };
        _listen = function( key, fn, cache ){ 
            if ( !cache[ key ] ){
                cache[ key ] = []; 
            }
           cache[key].push( fn );
        };
        _remove = function( key, cache, fn) {
            if ( cache[ key ] ){
                if( fn ){
                    for( var i = cache[ key ].length; i >= 0; i-- ){
                        if( cache[ key] [i] === fn) {
                            cache[key].splice(i, 1);
                        }
                    } 
                } else{
                    cache[ key ] = [];
                }
            } 
      };
      _trigger = function(){
           var cache = _shift.call(arguments),
                 key = _shift.call(arguments), 
                args = arguments,
               _self = this, 
               ret, 
               stack = cache[ key ];
          if ( !stack || !stack.length ) {
              return;
          }
          return each( stack, function(){
              return this.apply( _self, args );
          }); 
      };
      _create = function( namespace ){
          var namespace = namespace || _default;
          var cache = {},
              offlineStack = [],// 離線事件
              ret = {
                  listen: function(key, fn, last ){
                      _listen(key, fn, cache );
                      if ( offlineStack === null ){
                          return; 
                      }
                      if ( last === 'last' ){
                          offlineStack.length && offlineStack.pop()(); 
                      }else{
                          each( offlineStack, function(){
                              this(); 
                          });
                      }
                     offlineStack = null; 
                  },
                  one: function( key, fn, last ){ 
                      _remove( key, cache ); 
                      this.listen( key, fn ,last );
                  },
                  remove: function( key, fn ){
                      _remove( key, cache ,fn);
                  },
                  trigger: function(){
                      var fn, 
                          args,
                          _self = this;
                      _unshift.call( arguments, cache ); 
                      args = arguments;
                      fn = function(){
                          return _trigger.apply( _self, args ); 
                      };
                      if ( offlineStack ){
                          return offlineStack.push( fn );
                      }
                      return fn(); 
                  }
            };
            return namespace ?
                    ( namespaceCache[ namespace ] ? namespaceCache[ namespace] :
                          namespaceCache[ namespace ] = ret ) : ret;
        };
    return {
        create: _create,
        one: function( key,fn, last ){ 
            var event = this.create( );
            event.one( key,fn,last );
        },
        remove: function( key,fn ){
            var event = this.create( ); 
            event.remove( key,fn );
        },
        listen: function( key, fn, last ){
            var event = this.create( ); 
            event.listen( key, fn, last );
        },
        trigger: function(){
            var event = this.create( );
            event.trigger.apply( this, arguments ); 
        }
    }; }();
    return Event; 
})();

最後,給大家推薦一個前端學習進階內推交流羣685910553前端資料分享),不管你在地球哪個方位,
不管你參加工作幾年都歡迎你的入駐!(羣內會定期免費提供一些羣主收藏的免費學習書籍資料以及整理好的面試題和答案文檔!)

如果您對這個文章有任何異議,那麼請在文章評論處寫上你的評論。

如果您覺得這個文章有意思,那麼請分享並轉發,或者也可以關注一下表示您對我們文章的認可與鼓勵。

願大家都能在編程這條路,越走越遠。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章