aermin / blog

📝 My blog / notes

Home Page:https://www.aermin.top/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

js设计模式--发布订阅模式

aermin opened this issue · comments

定义:发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。

现实中的发布订阅:小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼 MM 告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。 但到底是什么时候,目前还没有人能够知道。运用发布订阅:小明离开之前,把电话号码留在 了售楼处。售楼 MM 答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一 样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册, 遍历上面的电话号码,依次发送一条短信来通知他们。

发送短信通知就是一个典型的发布—订阅模式,小明、小红等购买者都是 订阅者,他们订阅了房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话 号码,依次给购房者发布消息。

好处:

购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者 会通知这些消息订阅者。说明发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。

购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留 在售楼处,售楼处不关心购房者的任何情况,不管购房者是男是女还是一只猴子。 而售 楼处的任何变动也不会影响购买者,比如售楼 MM 离职,售楼处从一楼搬到二楼,这些 改变都跟购房者无关,只要售楼处记得发短信这件事情。说明发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调 用另外一个对象的某个接口。

DOM 事件

只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布—订阅 模式

document.body.addEventListener( 'click', function(){
     alert(2); 
}, false );
document.body.click();// 模拟用户点击

在这里需要监控用户点击 document.body 的动作,但是我们没办法预知用户将在什么时候点 击。所以我们订阅 document.body 上的 click 事件,当 body 节点被点击时,body 节点便会向订阅 者发布这个消息。

自定义事件

代码思路:如何一步步实现发布—订阅模式?

  • 首先要指定好谁充当发布者(比如售楼处);
  • 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册);
  • 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函 数(遍历花名册,挨个发短信)。

代码

var Event = (function(){
    var clientList = {},/ /缓存列表,存放订阅者的回调函数
          listen, 
         trigger,
         remove;
    //订阅(消息)
    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 ); // (2) // arguments 是 trigger 时带上的参数 
        }
     }
   //取消订阅事件
   remove = function( key, fn ){
     var fns = clientList[ key ]; 
     if ( !fns ){ return false; } // 如果 key 对应的消息没有被人订阅,则直接返回
     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( 'squareMeter88', function( price ){ // 小红订阅消息
    console.log( '价格= ' + price ); // 输出:'价格=2000000' 
});
Event.trigger( 'squareMeter88', 2000000 );// 售楼处发布消息