javascript中自定义事件和声明调用函数有什么区别?

最近在琢磨javascript的自定义事件,我的理解是注册一个事件(addEvent),然后在需要的时候调用它(fireEvent),这感觉像声明好一个函数,然后在需要的时候调用,感觉不出它们之间的区别。
我觉得任何事物的存在都有它的道理,那这自定义事件到底用在哪些场景?

阅读 8.5k
2 个回答

你的感觉是对的,实际上事件机制就是从回调函数转化而来的。

实际上事件模式算是订阅/发布模式的一种,它的好处在于绑定事件和触发事件是互相隔离的,并且可以动态的添加和删除,下面我帮你一步一步的梳理出来。

现在有这么一个需求,首先,我有一个动作Action1,我想每次在Action1完成之后,都会触发一个服务Service1,那么代码我可以这么写。

javascript// 服务1
function Service1(){}
// 动作1
function Action1(){
    //other things
    Service1();
}

这个就是楼主所想的,代码非常明了而且易懂,很不错。

然而现在需求来了,我的动作Action1,想在它完成以后,不仅能触发一个服务Service1,还能触发Service2,Service3……

你可能想到,应该声明另外一个叫ServiceAll的函数,然后在ServiceAll里依次调用Service1、Service2、Service3,这个主意是可行的。

javascriptfunction 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岂不是就好了?

javascriptvar 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要执行,这个时候最简单的实现办法是和上面一样,但如果直接复制粘贴代码的话,动作一多,重复代码就太多了,所以为了代码复用,我们写个统一的处理函数

javascriptvar 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)带进去,这是可行的,不过我们换个面向对象的思路

javascriptfunction 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);
    }
}

看到这里,聪明的你应该就清楚了,我们是在实现一个简易的自定义事件机制,把其中的名字换一下就能更清晰的看出来了。

javascriptfunction 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了。

所以题主你应该明白了事件模式的好处了吧?

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏