《JavaScript设计模式与开发实践》模式篇(5)—— 观察者模式

2,370

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 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; 
})();

系列文章:

《JavaScript设计模式与开发实践》最全知识点汇总大全