初始化Event对象html
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) // 删除订阅者的回调函数 } } } }, // 略
调用app
// 小红订阅以下消息 同时在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触发的时候,回调此方法,从而触发函数。
发布订阅模式比较适合写封装插件,我认为拿来写业务逻辑代码,有点不太好用。固然这只是我本身的观点。
接下来,我准备用这个模式封装一个上传的组件。函数
附录(参考文献)this