发布/订阅模式(Pub/Sub)是一种消息模式,它有两个参与者:发布者和订阅者。发布者向某个信道发布
一条消息,订阅者绑定这个信道,当有消息发布至信道时就会接收到一个通知。最重要的一点是,发布者和
订阅者是完全解耦的,彼此并不知晓对方的存在。两者仅仅共享一个信道名称。

这种模式在js里面有这天然的优势,因为js本身就是事件驱动型语言。比如,页面上有一个button,
你点击一下就会触发上面的click事件,而此时有一部分程序正在监听这个事件,随之触发相关的处理程序。

事实上,我们也早就熟悉这个模式了,只是不知道这叫什么(订阅发布模式 又名 观察者模式).
这个模式最大的一个好处就在于,能够解耦回调函数,让你的程序看起来更美观(虽然现在有Promise和

Deferred帮忙,但是不彻底)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>发布订阅模式</title>
</head>
<body>
<script>
    var pubsub = (function(){
        var q = {}, topics = {}, subUid = -1;
        //发布消息
        q.publish = function(topic, args) {
            if (!topics[topic]) {
                return;
            }
            var subs = topics[topic], len = subs.length;
            while(len--) {
                subs[len].func(topic, args);
            }
            return this;
        };
        //订阅事件
        q.subscribe = function(topic, func) {
            topics[topic] = topics[topic] ? topics[topic] : [];
            var token = (++subUid).toString();
            topics[topic].push({
                token : token,
                func : func
            });
            return token;
        };
        return q;
        //取消订阅就不写了,遍历topics,然后通过保存前面返回token,删除指定元素
    })();
    //触发的事件
    var logmsg = function(topics, data) {
        console.log("logging:" + topics + ":" + data);
    }
    //监听指定的消息'msgName'
    var sub = pubsub.subscribe('msgName', logmsg);
    //发布消息'msgName'
    pubsub.publish('msgName', 'hello world');
    //发布无人监听的消息'msgName1'
    pubsub.publish('anotherMsgName', 'me too!');
</script>
</body>

javascript 设计模式 发布订阅模式_JavaScript


javascript 设计模式 发布订阅模式_JavaScript_02


功能完善的发布订阅模式:

<!DOCTYPE html>  
<html>  
<head>  
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>  
</head>  
<body>   
<script> 

var $EventEmiter = (function () {
    function $EventEmiter () {
        this._events = {
        }
    }
    $EventEmiter.prototype = {
        on: function (eventName, callback) {
            if (this._events[eventName]) {
                //如果有就放一个新的
                this._events[eventName].push(callback);
            } else {
                //如果没有就创建一个数组
                this._events[eventName] = [callback];
            }
        },
        emit: function (eventName, caller, args) {
            if (this._events[eventName]) { //循环一次执行
                this._events[eventName].forEach(function(item) {
                    item.apply(caller, args)
                });                
            }          
        },
        removeListener: function (eventName, callback) {
            if (this._events[eventName]) {
                //当前数组和传递过来的callback相等则移除掉
                //this._events[eventName] = this._events[eventName].filter(item=>item!==callback);
                var temp = [];
                for (var i = 0; i < this._events[eventName].length; i++) {
                    if (this._events[eventName][i] !== callback) {
                        temp.push(this._events[eventName][i]);
                    }
                }
                this._events[eventName] = temp;
            }
        },
        removeListenerByEventName: function (eventName) {
            if (this._events[eventName]) {
                this._events[eventName] = [];
            }
        },        
        once: function(eventName, callback) {
            var that = this;
            function one() {
                //在one函数运行原来的函数,只有将one清空
                callback.apply(this, arguments);
                //先绑定 执行后再删除
                that.removeListener(eventName, one);
            }
            this.on(eventName, one);
            //此时emit触发会执行此函数,会给这个函数传递rest参数
        }        
    }
    return $EventEmiter;
})();

var EventEmiter = new $EventEmiter();

var msgName = function(data) {  
    console.log("msgName:" + data);  
}  
var sub = EventEmiter.on('msgName', msgName);  
var msgNames = function(data) {  
    console.log("msgNames:" + data);  
}  
var sub = EventEmiter.on('msgName', msgNames); 
EventEmiter.emit('msgName', this, ['hello world']);    
EventEmiter.emit('msgName', this, ['hello world2']);  
EventEmiter.removeListener('msgName', msgName);
EventEmiter.emit('msgName', this, ['hello world3']);  
EventEmiter.removeListenerByEventName('msgName');
EventEmiter.emit('msgName', this, ['hello world4']);  
EventEmiter.emit('msgName', this, ['hello world5']);  

var msgName2 = function(data) {  
    console.log("msgName2:" + ":" + data);  
}  
var sub = EventEmiter.once('msgName2', msgName2);  
EventEmiter.emit('msgName2', this, ['hello world']);    
EventEmiter.emit('msgName2', this, ['hello world2']);

EventEmiter.emit('anotherMsgName', this, ['me too!']);  

</script>  
</body>  
</html>  

使用es6实现:

<!DOCTYPE html>  
<html>  
<head>  
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>  
</head>  
<body>   
<script> 

//发布订阅模式
class EventEmiter{
  constructor(){
    //维护一个对象
    this._events={
 
    }
  }
  on(eventName,callback){
    if( this._events[eventName]){
      //如果有就放一个新的
      this._events[eventName].push(callback);
    }else{
      //如果没有就创建一个数组
      this._events[eventName]=[callback]
    }
  }
  emit(eventName,...rest){
    if(this._events[eventName]){ //循环一次执行
      this._events[eventName].forEach((item)=>{
        item.apply(this,rest)
      });
    }
  }
  removeListener(eventName,callback){
    if(this._events[eventName]){
      //当前数组和传递过来的callback相等则移除掉
      this._events[eventName]=
        this._events[eventName].filter(item=>item!==callback);
    }
  }
  once(eventName,callback){
    function one(){
      //在one函数运行原来的函数,只有将one清空
      callback.apply(this,arguments);
      //先绑定 执行后再删除
      this.removeListener(eventName,one);
    }
    this.on(eventName,one);
      //此时emit触发会执行此函数,会给这个函数传递rest参数
  }
}
class Man extends EventEmiter{}
let man=new Man()
function findGirl() {
  console.log('找新的女朋友')
}
function saveMoney() {
  console.log('省钱')
}
man.once('失恋',findGirl);
//man.on('失恋',findGirl) //失恋 ,绑定一个函数方法
man.on('失恋',saveMoney)//失恋 ,绑定一个函数方法
man.removeListener('失恋',saveMoney); //移除一个函数方法
man.emit('失恋');
//绑定一次,触发多次,也只执行一次。触发后一次将数组中的哪一项删除掉下次触发就不会执行
</script>  
</body>  
</html>