最近在琢磨javascript的自定义事件,我的理解是注册一个事件(addEvent),然后在需要的时候调用它(fireEvent),这感觉像声明好一个函数,然后在需要的时候调用,感觉不出它们之间的区别。
我觉得任何事物的存在都有它的道理,那这自定义事件到底用在哪些场景?
http://www.infoq.com/cn/articles/tyq-nodejs-event
你的感觉是对的,实际上事件机制就是从回调函数转化而来的。
实际上事件模式算是订阅/发布模式的一种,它的好处在于绑定事件和触发事件是互相隔离的,并且可以动态的添加和删除,下面我帮你一步一步的梳理出来。
现在有这么一个需求,首先,我有一个动作Action1,我想每次在Action1完成之后,都会触发一个服务Service1,那么代码我可以这么写。
javascript
// 服务1 function Service1(){} // 动作1 function Action1(){ //other things Service1(); }
这个就是楼主所想的,代码非常明了而且易懂,很不错。
然而现在需求来了,我的动作Action1,想在它完成以后,不仅能触发一个服务Service1,还能触发Service2,Service3……
你可能想到,应该声明另外一个叫ServiceAll的函数,然后在ServiceAll里依次调用Service1、Service2、Service3,这个主意是可行的。
javascript
function Service1(){} function Service2(){} function Service3(){} function ServiceAll(){ Service1(); Service2(); Service3(); } // 动作1 function Action1(){ //other things ServiceAll(); }
但问题随之而来,这里的ServiceAll事先定义好了,假如我要动态的添加一个Service4怎么办?假如我突然改变心意,动作Action1以后不再执行Service2,这个时候代码正在运行,必须重新定义ServiceAll函数,这无论开销还是实现都不方便。
我们再换个思路,我们弄一个叫ServiceArray的数组,然后把所有要执行的Service都放进去,动作Action1以后,直接循环调用ServiceArray岂不是就好了?
javascript
var ServiceArray=[]; // 动作1 function Action1(){ //other things ServiceArray.each(function(Service){ Service(); }) } //追加Service1,Service2,Service3 ServiceArray.push(Service1); ServiceArray.push(Service2); ServiceArray.push(Service3); // 实现一个数组删除函数 function deleteVal(arr,val){ var index = arr.indexOf(val); if (index !== -1) { arr.splice(index, 1); } } // 动态增加一个Service4,并且不要Service2了 ServiceArray.push(Service4); deleteVal(ServiceArray,Service2);
好了,我们已经用数组队列来轻松实现动态删除和动态添加Service的功能了,接下来又有一个新的需求。
我们上面都是一个动作Action1,我现在有第二个动作Action2,它也要有一堆Service要执行,这个时候最简单的实现办法是和上面一样,但如果直接复制粘贴代码的话,动作一多,重复代码就太多了,所以为了代码复用,我们写个统一的处理函数
javascript
var actionObj={}; function hanldeAction(name,serviceArr){ if(typeof actionObj[name] !=funtion){ actionObj[name]=function(){ serviceArr.forEach(function(Service){ Service(); }) } } return actionObj[name]; } //调用 Action1=hanldeAction("action1",ServieArray1); Action2=handleAction("action2",ServieArray2);
我们上面考虑的这些Action1,Action2都是相互独立的情况,下面再考虑一个问题,假设这些动作都是某个对象obj发出来的,我们必须要保证这个对象在Service1,Service2等等都能访问到,这个时候该怎么办?
你可能想到把obj带在handleAction的参数里,然后再其定义里由Servie(obj)带进去,这是可行的,不过我们换个面向对象的思路
javascript
function MyObj(){ this.actions={}; } // 动态给Action添加Service MyObj.prototype.addService=function(actionName,Service){ if(!this.actions[actionName])this.actions[actionName]=[]; this.actions[actionName].push(Service); } // 动态删除Service MyObj.prototype.removeService=function(actionName,Service){ var ServiceArray=this.actions[actionName]?this.actions[actionName]:[]; var index = ServiceArray.indexOf(Service); if (index !== -1) { ServiceArray.splice(index, 1); } } // Action调用Service MyObj.prototype.emitAction=function(actionName){ var ServiceArray=this.actions[actionName]?this.actions[actionName]:[]; for(var i=0;i<ServiceArray.length;i++){ ServiceArray[i].apply(this); } }
看到这里,聪明的你应该就清楚了,我们是在实现一个简易的自定义事件机制,把其中的名字换一下就能更清晰的看出来了。
javascript
function EventEmitter(){ this._events={}; } EventEmitter.prototype.addListener=function(type, listener){ if(!this._events[type])this._events[type]=[]; this._events[type].push(listener); } EventEmitter.prototype.removeListener=function(type, listener){ var listenerArray=this._events[type]?this._events[type]:[]; var index = listenerArray.indexOf(listener); if (index !== -1) { listenerArray.splice(index, 1); } } EventEmitter.prototype.emit=function(type){ var listenerArray=this._events[type]?this._events[type]:[]; for(var i=0;i<listenerArray.length;i++){ listenerArray[i].apply(this); } }
这样我们就能通过new一个EventEmitter来使用它了。
javascript
//使用 var a = new EventEmitter(); a.addListener("type1",function(){ console.log("service1"); }) a.addListener("type1",function(){ console.log("service2"); }) a.emit("type1"); function otherService(){ console.log("other service"); } a.addListener("type2",otherService); a.addListener("type2",function(){console.log("service 3")}) a.emit("type2"); a.removeListener("type2",otherService); a.emit("type2");
这个简单的事件对象已经可以满足我上面提到的种种要求了,但还缺少附加参数、移除所有事件、限定域等等功能,把这些完善以后,再优化一下事件队列的存储方式,你就完成了一个真正的EventEmitter了。
所以题主你应该明白了事件模式的好处了吧?