zoukankan      html  css  js  c++  java
  • JavaScript设计模式(发布订阅模式)

    发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式。

    一、简单的发布-订阅模式

    这里以售楼处的售楼消息为例,小明和小红是消息订阅者,售楼处是发布消息者,一旦售楼处有消息就会主动向小明和小红发送消息
    var salesOffices = {};  // 定义售楼处
    salesOffices.clientList = [];  // 缓存列表,存放订阅者的回调函数
    salesOffices.listen = function( fn ){  // 增加订阅者 
      this.clientList.push( fn );  // 订阅的消息添加进缓存列表
    };
    salesOffices.trigger = function(){ // 发布消息 
      for( var i = 0, fn; fn = this.clientList[ i++ ];) {
        fn.apply( this, arguments ); // arguments 是发布消息时带上的参数
      } 
    };
    下面我们来进行一些简单的测试:
    salesOffices.listen( function( price, squareMeter ) {   // 小明订阅者
      console.log( '价格= ' + price );
      console.log( 'squareMeter= ' + squareMeter );
    });
    
    salesOffices.listen( function( price, squareMeter ) {   // 小红订阅者
      console.log( '价格= ' + price );
      console.log( 'squareMeter= ' + squareMeter );
    });
    
    salesOffices.trigger( 2000000, 88 ); // 输出: 价格= 2000000,squareMeter= 88   价格= 2000000,squareMeter= 88  
    salesOffices.trigger( 3000000, 110 ); // 输出: 价格= 3000000,squareMeter= 110  价格= 3000000,squareMeter= 110
    这里还存在一些问题。我们看到订阅者接收到了发布者发布的每个消息,虽然小明只想买 88 平方米的房子,但是发布者把110平方米的信息也推送给了小明,这对小明来说是不必要的困扰。所以我们有必要增加一个标示key, 让订阅者只订阅自己感兴趣的消息。所以我们需要改造一下代码:
    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 ); // arguments 是发布消息时带上的参数
      } 
    };
    
    salesOffices.listen( 'squareMeter88', function( price ) {   // 小明订阅者
      console.log( '价格= ' + price );
    });
    
    salesOffices.listen( 'squareMeter110', function( price ) {   // 小红订阅者
      console.log( '价格= ' + price );
    });
    
    salesOffices.trigger( 'squareMeter88', 2000000);   // 输出: 价格= 2000000 
    salesOffices.trigger( 'squareMeter110', 3000000);  // 输出: 价格= 3000000

    二、发布-订阅模式的通用实现

    这段代码是否必须在另一个售楼处对象上重写一次呢,有没有办法可以让所有对象都拥有发布—订阅功能呢? 
    首先,我们把发布—订阅的功能提取出来,放在一个单独的对象内:
    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 时带上的参数
        } 
      }
    }
    再定义一个 installEvent 函数,这个函数可以给所有的对象都动态安装发布—订阅功能:
    var installEvent = function( obj ){ 
      for ( var i in event ){
        obj[ i ] = event[ i ];
      }
    };
    var salesOffices = {};
    installEvent( salesOffices );
    salesOffices.listen( 'squareMeter88', function( price ){ 
      console.log( '价格= ' + price );      // 小明订阅消息
    });
    salesOffices.listen( 'squareMeter100', function( price ){ 
      console.log( '价格= ' + price );      // 小红订阅消息
    });
    salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000
    salesOffices.trigger( 'squareMeter100', 3000000 ); // 输出:3000000
    取消订阅的事件:
    event.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

    三、全局的发布-订阅对象

    发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消 息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者” 的角色,把订阅者和发布者联系起来
    var Event = (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( var i = 0, fn; fn = fns[ i++ ]; ){ 
          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 = 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( 'squareMeter88', function( price ){  // 小红订阅消息
        console.log( '价格= ' + price );   // 输出:'价格=2000000'  
    });
    Event.trigger( 'squareMeter88', 2000000 ); // 售楼处发布消息

    四、总结

      发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常 广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布—订阅模式还可 以用来帮助实现一些别的设计模式,比如中介者模式。从架构上来看,无论是 MVC 还是 MVVM, 都少不了发布—订阅模式的参与,而且 JavaScript 本身也是一门基于事件驱动的语言。
      当然,发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而 且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外, 发布—订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联 系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一 起的时候,要跟踪一个 bug 不是件轻松的事情。
     
  • 相关阅读:
    iot 表 主键索引叶子块包含了表所有数据
    iot 表索引dump《2》
    iot 表索引dump《2》
    heap表和iot表排序规则不同
    heap表和iot表排序规则不同
    ActiveMQ学习总结(4)——业界消息队列简介
    主宰全球的10大算法
    主宰全球的10大算法
    主宰全球的10大算法
    SVN学习总结(1)——SVN简介及入门使用
  • 原文地址:https://www.cnblogs.com/angelatian/p/11858596.html
Copyright © 2011-2022 走看看