今天在浏览JavaScript事件时,复习了下Dean Edward大神的addEvent。突然觉得可以基于他的思路实现一个结构更好的PubSub。
思路也很简单,就是要维护一个类似如下的一个仓库结构:
/*
{
'sayHello': {
0: fn0,
1: fn1,
//... },
'sayGoodBye': {
0: fnn,
//...
},
//...
}*/
下面是我的实现代码:
(function(exports) {
var PubSub = exports.PubSub || {}; //在PubSub对象上增加静态域PubSubCache,用于保存subscribe相关数据 /**
* PubSub.PubSubCache仓库结构
* {
* 'sayHello': {
* 0: fn0,
* 1:fn1,
* //..。
* },
* 'sayGoodBye': {
* //...
* }
* }
*
*/
PubSub.PubSubCache = PubSub.PubSubCache || {$uid: 0}; //PubSub有4个静态方法:subscribe, subscribeOne, unsubscribe, publish
//PubSub不会与DOM元素有关系。这样publish也只能手动去触发了
PubSub.subscribe = function(type, handler) {
var cache = this.PubSubCache[type] || (this.PubSubCache[type] = {});
handler.$uid = handler.$uid || this.PubSubCache.$uid++; //把回调放入仓库中
cache[handler.$uid] = handler;
}; PubSub.unsubscribe = function(type, handler) {
var counter = 0,$type, cache = this.PubSubCache[type]; if(arguments.length === 1) {
//直接删除此种类型的订阅对象
if(!cache) return true;
return !!this.PubSubCache[type] && (delete this.PubSubCache[type]);
} else if(arguments.length === 2) {
!!this.PubSubCache[type] && (delete this.PubSubCache[type][handler.$uid]);
} //PubSubCahe仓库中某类型订阅为空,则要删除这个订阅对象
for($type in cache) {
counter++;
} return !counter && (delete this.PubSubCache[type]);
}; PubSub.publish = function(type) {
var cache = this.PubSubCache[type], key, oneFlag, tmp, context, args = [].slice.call(arguments); if(!cache) return; if(args.length === 1) {
context = exports;
} else {
context = args[1];
} //执行回调
for(key in cache) {
tmp = cache[key];
//在发布消息时可以指定回调函数的上下文,同时还可以传入参数
cache[key].apply(context, args.slice(1));
tmp.one && this.unsubscribe(type, tmp);
}
}; PubSub.subscribeOne = function(type, handler) {
this.subscribe(type, handler);
//给函数加一个只执行一次的标志
handler.one = true;
}; exports.PubSub = PubSub;
})(window);
下面是测试代码:
var data = {name: 'haha', age:18}; var handler2 = function(data) {
console.log('say.hello excuted! 2');
console.log(this.name);
}; //订阅say.hello消息
PubSub.subscribe('say.hello', function(data) {
console.log('say.hello excuted! 1');
}); //第二次订阅say.hello消息
PubSub.subscribe('say.hello', handler2); //第三次订阅say.hello消息
PubSub.subscribe('say.hello', function(data) {
console.log('say.hello excuted! 3');
console.log(this.age);
}); //第四次增加一个只会执行一次的say.hello消息订阅
PubSub.subscribeOne('say.hello', function(data) {
console.log('say.hello excuted! one');
}); /**
* 发布say.hello消息类型
* 输出:
* say.hello excuted! 1
* say.hello excuted! 2
* haha
* say.hello excuted! 3
* 18
* say.hello excuted! one
*/ PubSub.publish('say.hello', data); //取消第二次订阅的say.hello类型
PubSub.unsubscribe('say.hello', handler2); /**
* 发布say.hello消息类型
* 输出:
* say.hello excuted! 1
* say.hello excuted! 3
* 18
*/ console.log('--------------------------------')
PubSub.publish('say.hello', data); /**
* 再次发布say.hello消息,不过这次除了传入执行上下文外,还要传入参数
* 输出:
* say.hello excuted! 1
* say.hello excuted! 3
* 18
* say.hello excuted! has deliverd args
* args123
*/ PubSub.subscribe('say.hello', function(data, args) {
console.log('say.hello excuted! has deliverd args');
console.log(args);
}); console.log('--------------------------------')
PubSub.publish('say.hello', data, 'args123');
小结:
全局的PubSub对象有四个方法:
1. subscribe(type, handler) :增加一种类型的消息订阅。类似jQuery的bind();
2. subscribeOne(type, handler):增加一种回调只会执行一次的消息订阅,类似jQuery的one()
3. unsubscribe(type, [handler]): 如果只传type,会删除所有的type消息订阅;传入了回调函数,则只删除那一个回调。类似jQuery的unbind()
4. publish(type):执行订阅。类似jQuery的trigger();
当然上面的功能Cowboy大神只用了只行代码就实现了(基于jQuery):
(function($) {
//得到一个jQuery对象,以便使用其方法
var o = $({}); //为jQuery对象增加静态的方法
$.subscribe = function() {
o.bind.apply(o, arguments);
}; $.unsubscribe = function() {
o.unbind.apply(o, arguments);
}; $.publish = function() {
o.trigger.apply(o, arguments);
} })(jQuery);