Skip to content

5、订阅发布者模式.md

  • 发布—订阅模式又叫观察者模式

  • 它定义对象间的一种一对多的依赖关系,

  • 当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

  • 在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式

  • 优点:

    • 广泛应用于异步编程中,这是一种替代传递回调函数的方案,无需过多关注对象在异步运行期间的内部状态,而只需要订阅感兴趣的事件发生点
    • 取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口,取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口
  • addEventListener 典型的订阅发布

js
        var salesOffices = {};    // 定义售楼处

        salesOffices.clientList = {};    // 缓存列表,存放订阅者的回调函数

        salesOffices.listen = function( key, fn ){
            if ( ! this.clientList[ key ] ){    // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
              this.clientList[ key ] = [];
            }
            this.clientList[ key ].push( fn );    // 订阅的消息添加进消息缓存列表
        };

        salesOffices.trigger = function(){    // 发布消息
            var key = Array.prototype.shift.call( arguments ),    // 取出消息类型
              fns = this.clientList[ key ];    // 取出该消息对应的回调函数集合

            if ( ! fns || fns.length === 0 ){    // 如果没有订阅该消息,则返回
              return false;
            }

            for( var i = 0, fn; fn = fns[ i++ ]; ){
              fn.apply( this, arguments );    // (2) // arguments是发布消息时附送的参数
            }
        };

        salesOffices.listen( 'squareMeter88', function( price ){    // 小明订阅88平方米房子的消息
            console.log( '价格= ' + price );    // 输出: 2000000
        });

        salesOffices.listen( 'squareMeter110', function( price ){     // 小红订阅110平方米房子的消息
            console.log( '价格= ' + price );    // 输出: 3000000
        });

        salesOffices.trigger( 'squareMeter88', 2000000 );     // 发布88平方米房子的价格
        salesOffices.trigger( 'squareMeter110', 3000000 );    // 发布110平方米房子的价格
js
    var event = {
        clientList: [],
        listen: function( key, fn ){
            if ( ! this.clientList[ key ] ){
                this.clientList[ key ] = [];
            }
            this.clientList[ key ].push( fn );    // 订阅的消息添加进缓存列表
        },
        trigger: function(){
            var key = Array.prototype.shift.call( arguments ),    // (1);
                fns = this.clientList[ key ];

            if ( ! fns || fns.length === 0 ){    // 如果没有绑定对应的消息
                return false;
            }

            for( var i = 0, fn; fn = fns[ i++ ]; ){
                fn.apply( this, arguments );    // (2) // arguments是trigger时带上的参数
            }
        },
        remove : function( key, fn ){
            var fns = this.clientList[ key ];

            if ( ! fns ){    // 如果key对应的消息没有被人订阅,则直接返回
                return false;
            }
            if ( ! fn ){    // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
                fns && ( fns.length = 0 );
            }else{
                for ( var l = fns.length -1; l >=0; l-- ){    // 反向遍历订阅的回调函数列表
                    var _fn = fns[ l ];
                    if ( _fn === fn ){
                        fns.splice( l, 1 );    // 删除订阅者的回调函数
                    }
                }
            }
        };
    };

    var salesOffices = {};
    var installEvent = function( obj ){
        for ( var i in event ){
            obj[ i ] = event[ i ];
        }
    }

    installEvent( salesOffices );

    salesOffices.listen( 'squareMeter88', fn1 = function( price ){    // 小明订阅消息
        console.log( ’价格= ' + price );
    });

    salesOffices.listen( 'squareMeter88', fn2 = function( price ){    // 小红订阅消息
        console.log( ’价格= ' + price );
    });

    salesOffices.remove( 'squareMeter88', fn1 );    // 删除小明的订阅
    salesOffices.trigger( 'squareMeter88', 2000000 );     // 输出:2000000
js
    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 ] === 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' ){
						}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;
	})();