The publisk-subscribe pattern, also known as the observer pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified. In JavaScript development, we generally use the event model instead of the traditional publish-subscribe model.

The story background

Xiao Ming recently had his eye on an apartment, but when he arrived at the sales office, he was told that the apartment was sold out. Fortunately, the sales MM told Xiao Ming, there are some end of the launch before long, developers are dealing with relevant procedures, procedures done can be bought. But when exactly, no one knows yet. So Xiaoming made a note of the sales office telephone, after every day will call the past to ask whether it has arrived to buy time. In addition to Xiao Ming, and xiao Hong, Xiao Qiang, xiao Long will consult the sales office every day. A week later, the sales manager decided to quit because she was tired of answering 1,000 phone calls a day with the same content. Of course, there are no such stupid sales companies in real life. In fact, the story goes like this: Before Xiao Ming left, he left his phone number at the sales office. Sales MM promised him, a new project launched immediately send information notice Xiaoming. Red, xiaoqiang and xiaolong are the same, their phone numbers are recorded in the sales office roster, the launch of the new property, sales MM will open the roster, traverse the above phone number, in turn to send a text message to inform them.

SMS notification is a typical publishing-subscribe model, in which buyers such as Xiao Ming and Xiao Hong are subscribers who subscribe to the news of the house sale. The sales office, as a publisher, will traverse the phone numbers on the roster at the right time and release information to buyers in turn.

Code implementation

  • Subscribe now publish
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 (letindex = 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){// Small red subscription message console.log('Price ='+ price ); / / output:'price = 2000000'
});
Event.trigger( 'squareMeter88', 2000000); // Sales office releases informationCopy the code

Application scenarios

  • Let’s say we’re developing a mall site with a header, nav navigation, message list, shopping cart, and other modules. The common premise for rendering of these modules is that an Ajax asynchronous request must be made to obtain the user’s login information. This is normal, for example, for the user’s name and avatar to be displayed in the header module, and both fields come from the information returned by the user after login. There is no way of knowing when an Ajax request will successfully return user information. Now the plot looks very much like the sales office example, Xiao Ming does not know when the developer’s sales procedures can be successfully done down.
$.ajax( 'http:// xxx.com?login'.functionLogin.trigger (data){// Successful login'loginSucc', data); // Publish a successful login message}); var header = (function(){// Header module login.listen('loginSucc'.function( data){
        header.setAvatar( data.avatar );
    }); 
    return {
        setAvatar: function( data ){
            console.log( 'Set the header image'); }}}) (); var nav = (function(){
    login.listen( 'loginSucc'.function(data){// nav.setavatar (data.avatar); });return {
        setAvatar: function( avatar ){ 
            console.log( 'Set the avatar of nav module'); }}}) ();Copy the code
  • Publish now subscribe (provides namespace creation capability)
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 = [],// offline event 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 );
                      }
                      returnfn(); }};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 ); 
        }
    }; }();
    returnEvent; }) ();Copy the code

Series of articles:

JavaScript Design Patterns and Development Practices