观察者模式又叫发布-订阅模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生该变时,所有依赖于它的对象都将得到通知。在JavaScript中,一般用事件模型来替代传统的观察者模式。
     下面是售楼处(发布者)与各看房者(订阅者)的例子:

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),
                         fns = this.clientList[key];
                    if(!fns || fns.length == 0){   //没有绑定对应的消息
                            return false;    
                    }
                    for(var i=0,fn; fn=fns[i++]){
                            fn.apply(this, arguments);
                    }
            },           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 installEvent = function(obj){    //给所有对象动态安装发布-订阅功能
          for(var i in event){
               obj[i] = event[i];
          }
      }; 
      var salesOffices = {};    //定义售楼处
      installEvent(salesOffices); 
      salesOffices.listen('squareMeter100',function(price){    // 张三订阅消息
            console.log('价格=' + price);
      });      salesOffices.listen('squareMeter150',function(price){    // 李四订阅消息
            console.log('价格=' + price);
      }); 
      salesOffices.trigger('squareMeter100',2000000);    // 输出 2000000
      salesOffices.trigger('squareMeter150',3000000);    // 输出 3000000

      上面的代码还存在两个小问题:
           1.每个发布者对象都添加了listen和trigger方法,以及一个缓存列表clientList,这是一种资源浪费
           2.订阅者跟售楼处对象存在一定的耦合性,订阅者至少要知道售楼处对象的名字是salesOffices,才能订阅到事件

      下面是对以上两个问题的改良:

var event = {
           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(var i=0,fn; fn=fns[i++]){
                     fn.apply(this, arguments);
                }
           }; 
           remove = function(key,fn){    //删除订阅
                   var fns = 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);    //删除订阅者的回调函数
                            }
                       }
                  }
             };

            return {
                   listen:listen,
                   trigger:trigger,
                   remove:remove
            };
     }; 
     Event.listen('squareMeter150',function(price){    // 李四订阅消息
              console.log('价格=' + price);
     }); 
      Event.trigger('squareMeter150',2000000);    // 输出 2000000

      改良后,发布-订阅模式可以用一个全局的Event对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不需要了解消息会推送给哪些订阅者,Event作为类似“中介者”的角色,把订阅者和发布者联系起来。

 

      观察者模式的优点非常明显,一为时间上的解耦,二为对象间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以用来编写更松耦合的代码编写。但也不是没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息始终都没发生,但这个订阅者会始终存在于内存中。另外,观察者模式虽然可以弱化对象间的联系,但如果过度使用的话,对象间的必要联系也将被深藏在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个bug不是键轻松的事。