今天在浏览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);