初始化Event对象

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

主代码:

var event = {
    list: [],
    listen: function(key, fn) {
        // 确定监听的事件容器默认是一个空数组
        if(!this.list[key]) {
            this.list[key] = [];
        }
        // 订阅的消息添加到缓存列表中
        this.list[key].push(fn);
        // 链式调用
        return this 
    },
    trigger: function(){
        // 获取trigger 函数参数的第一个参数,即key键
        // 此时arguments 是trigger的参数类数组
        var key = Array.prototype.shift.call(arguments);
        // 拿到对应key的监听事件数组
        var fns = this.list[key];
        // 如果没有订阅过该消息的话,则返回
        if(!fns || fns.length === 0) {
            return;
        }
        for(var i = 0, fn;i < fns.length; i ++) {
            fn = fns[i]
            //逐个调用key键所对应监听事件数组函数
            // 此时arguments 同样也是trigger的参数类数组,只不过少了第一个参数
            // 将此参数传递给fn函数作为形参
            // this 也是fn的执行作用域
            fn.apply(this, arguments);
        }
    }
   };

调用执行:

    // 新建一个比如小红的对象
    var shoeObj = {};
    // 初始化小红对象
    initEvent(shoeObj);
    // 小红同时订阅如下消息(链式调用) 
    shoeObj.listen('red',function(size, price){
        console.log("尺码是:"+size);  
        console.log('price是' +price)
    }).listen('block', function (size, price) {
        console.log("尺码是:"+size);  
        console.log('price是' +price)
    })
    
    shoeObj.trigger("red", 40, 500);

    shoeObj.trigger("block",42, 300); 

订阅了消息后,我们可能会remove掉消息,所以Event对象新增一个方法:

// 略
remove : function(key, fn) {
       var fns = this.list[key]
       // 如果key对应的消息没有订阅过的话,则返回
       if(!fns) return
       // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
       if(!fn) {
         fns.length = 0 // 或者this.list[key] = []
         // fns = []   
         //fns = [] 这样写后,实际this.list[key]中的回调数组
         //依然存在,因为初始fns指向this.list[key]这个数组(数组是一个引用类型)
         //fns = [],代表我们将fns又指向了一个新的数组长度为空的引用数组,而这个
         // 新的引用数组 和this.list[key]这个引用数组是计算机里面占用两个不同的
         // 堆栈。
       }else {
         for(var i = 0; i < fns.length; i ++) {
           var _fn = fns[i]
           if(_fn === fn) {
              fns.splice(i, 1) // 删除订阅者的回调函数
           }
         }
       }
    },
// 略

调用

    // 小红订阅如下消息 同时在red上面订阅了两个消息
    // 注意fn1 和fn2 这种写法,比较少见,实际fn1,fn2成为了一个全局变量
    // 在remove的时候,作为具体的参数传递
    shoeObj.listen('red',fn1 = function(size, price){
        console.log("尺码是1----" +size);  
        console.log('price是1----' +price)
    }).listen('red', fn2 = function(size, price){
        console.log("尺码是2----" +size);  
        console.log('price是2----' +price)
    })
    
    //remove 掉fn2
    shoeObj.remove('red', fn2)
    // 触发回调 此时只会回调fn1
    shoeObj.trigger("red", 40, 500);
    
    //如果remove 不传参数,就会将red中所有的监听全部remove掉
    shoeObj.remove('red')
    shoeObj.trigger("red", 40, 500);

结束语
发布订阅模式是js中36中设计模式中最常见的模式,也是很重要的设计模式。其实我们在写项目逻辑代码的时候,无形中也运用了这个思想,最常见的是click触发回调。比如我们定义一个方法,在定义的时候已经listen在一个对象上,或window对象上。在click触发的时候,回调此方法,从而触发函数。
发布订阅模式比较适合写封装插件,我认为拿来写业务逻辑代码,有点不太好用。当然这只是我自己的观点。
接下来,我准备用这个模式封装一个上传的组件。

附录(参考文献)

  1. cn一篇博文

健儿
79 声望4 粉丝

掌握好原生js。