初始化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触发的时候,回调此方法,从而触发函数。
发布订阅模式比较适合写封装插件,我认为拿来写业务逻辑代码,有点不太好用。当然这只是我自己的观点。
接下来,我准备用这个模式封装一个上传的组件。
附录(参考文献)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。