详解JavaScript中的自定义事件编写
我们可以自定义事件来实现更灵活的开发,事件用好了可以是一件很强大的工具,基于事件的开发有很多优势(后面介绍)。
与自定义事件的函数有Event、CustomEvent和dispatchEvent。
直接自定义事件,使用Event构造函数:
varevent=newEvent('build'); //Listenfortheevent. elem.addEventListener('build',function(e){...},false); //Dispatchtheevent. elem.dispatchEvent(event);
CustomEvent可以创建一个更高度自定义事件,还可以附带一些数据,具体用法如下:
varmyEvent=newCustomEvent(eventname,options);
其中options可以是:
{ detail:{ ... }, bubbles:true, cancelable:false }
其中detail可以存放一些初始化的信息,可以在触发的时候调用。其他属性就是定义该事件是否具有冒泡等等功能。
内置的事件会由浏览器根据某些操作进行触发,自定义的事件就需要人工触发。dispatchEvent函数就是用来触发某个事件:
element.dispatchEvent(customEvent);
上面代码表示,在element上面触发customEvent这个事件。结合起来用就是:
//addanappropriateeventlistener obj.addEventListener("cat",function(e){process(e.detail)}); //createanddispatchtheevent varevent=newCustomEvent("cat",{"detail":{"hazcheeseburger":true}}); obj.dispatchEvent(event);
使用自定义事件需要注意兼容性问题,而使用jQuery就简单多了:
//绑定自定义事件 $(element).on('myCustomEvent',function(){}); //触发事件 $(element).trigger('myCustomEvent'); 此外,你还可以在触发自定义事件时传递更多参数信息: $("p").on("myCustomEvent",function(event,myName){ $(this).text(myName+",hithere!"); }); $("button").click(function(){ $("p").trigger("myCustomEvent",["John"]); });
JavaScript自定义事件就是有别于如click,submit等标准事件的自行定制的事件,在叙述自定义事件有何好处之前,先来看一个自定义事件的例子:
<divid="testBox"></div> //创建事件 varevt=document.createEvent('Event'); //定义事件类型 evt.initEvent('customEvent',true,true); //在元素上监听事件 varobj=document.getElementById('testBox'); obj.addEventListener('customEvent',function(){ console.log('customEvent事件触发了'); },false);
具体效果可以查看Demo,在console中输入obj.dispatchEvent(evt),可以看到console中输出“customEvent事件触发了”,表示自定义事件成功触发。
在这个过程中,createEvent方法创建了一个空事件evt,然后使用initEvent方法定义事件的类型为约定好的自定义事件,再对相应的元素进行监听,接着,就是使用dispatchEvent触发事件了。
没错,自定义事件的机制如普通事件一样——监听事件,写回调操作,触发事件后执行回调。但不同的是,自定义事件完全由我们控制触发时机,这就意味着实现了一种JavaScript的解耦。我们可以把多个关联但逻辑复杂的操作利用自定义事件的机制灵活地控制好。
当然,可能你已经猜到了,上面的代码在低版本的IE中并不生效,事实上在IE8及以下版本的IE中并不支持createEvent(),而有IE私有的fireEvent()方法,但遗憾的是,fireEvent只支持标准事件的触发。因此,我们只能使用一个特殊而简单的方法触发自定义事件。
//type为自定义事件,如type='customEvent',callback为开发者实际定义的回调函数 obj[type]=0; obj[type]++; obj.attachEvent('onpropertychange',function(event){ if(event.propertyName==type){ callback.call(obj); } });
这个方法的原理实际上是在DOM中增加一个自定义属性,同时监听元素的propertychange事件,当DOM的某个属性的值发生改变时就会触发propertychange的回调,再在回调中判断发生改变的属性是否为我们的自定义属性,若是则执行开发者实际定义的回调。从而模拟了自定义事件的机制。
为了使到自定义事件的机制能配合标准事件的监听和模拟触发,这里给出一个完整的事件机制,这个机制支持标准事件和自定义事件的监听,移除监听和模拟触发操作。需要注意的是,为了使到代码的逻辑更加清晰,这里约定自定义事件带有'custom'的前缀(例如:customTest,customAlert)。
/** *@description包含事件监听、移除和模拟事件触发的事件机制,支持链式调用 * */ (function(window,undefined){ varEv=window.Ev=window.$=function(element){ returnnewEv.fn.init(element); }; //Ev对象构建 Ev.fn=Ev.prototype={ init:function(element){ this.element=(element&&element.nodeType==1)?element:document; }, /** *添加事件监听 * *@param{String}type监听的事件类型 *@param{Function}callback回调函数 */ add:function(type,callback){ var_that=this; if(_that.element.addEventListener){ /** *@supportedForModernBrowersandIE9+ */ _that.element.addEventListener(type,callback,false); }elseif(_that.element.attachEvent){ /** *@supportedForIE5+ */ //自定义事件处理 if(type.indexOf('custom')!=-1){ if(isNaN(_that.element[type])){ _that.element[type]=0; } varfnEv=function(event){ event=event?event:window.event if(event.propertyName==type){ callback.call(_that.element); } }; _that.element.attachEvent('onpropertychange',fnEv); //在元素上存储绑定的propertychange的回调,方便移除事件绑定 if(!_that.element['callback'+callback]){ _that.element['callback'+callback]=fnEv; } //标准事件处理 }else{ _that.element.attachEvent('on'+type,callback); } }else{ /** *@supportedForOthers */ _that.element['on'+type]=callback; } return_that; }, /** *移除事件监听 * *@param{String}type监听的事件类型 *@param{Function}callback回调函数 */ remove:function(type,callback){ var_that=this; if(_that.element.removeEventListener){ /** *@supportedForModernBrowersandIE9+ */ _that.element.removeEventListener(type,callback,false); }elseif(_that.element.detachEvent){ /** *@supportedForIE5+ */ //自定义事件处理 if(type.indexOf('custom')!=-1){ //移除对相应的自定义属性的监听 _that.element.detachEvent('onpropertychange',_that.element['callback'+callback]); //删除储存在DOM上的自定义事件的回调 _that.element['callback'+callback]=null; //标准事件的处理 }else{ _that.element.detachEvent('on'+type,callback); } }else{ /** *@supportedForOthers */ _that.element['on'+type]=null; } return_that; }, /** *模拟触发事件 *@param{String}type模拟触发事件的事件类型 *@return{Object}返回当前的Kjs对象 */ trigger:function(type){ var_that=this; try{ //现代浏览器 if(_that.element.dispatchEvent){ //创建事件 varevt=document.createEvent('Event'); //定义事件的类型 evt.initEvent(type,true,true); //触发事件 _that.element.dispatchEvent(evt); //IE }elseif(_that.element.fireEvent){ if(type.indexOf('custom')!=-1){ _that.element[type]++; }else{ _that.element.fireEvent('on'+type); } } }catch(e){ }; return_that; } } Ev.fn.init.prototype=Ev.fn; })(window); 测试用例1(自定义事件测试) //测试用例1(自定义事件测试) //引入事件机制 //... //捕捉DOM vartestBox=document.getElementById('testbox'); //回调函数1 functiontriggerEvent(){ console.log('触发了一次自定义事件customConsole'); } //回调函数2 functiontriggerAgain(){ console.log('再一次触发了自定义事件customConsole'); } //封装 testBox=$(testBox); //同时绑定两个回调函数,支持链式调用 testBox.add('customConsole',triggerEvent).add('customConsole',triggerAgain);
完整的代码在Demo。
打开Demo后,在console中调用testBox.trigger('customConsole')自行触发自定义事件,可以看到console输出两个提示语,再输入testBox.remove('customConsole',triggerAgain)移除对后一个监听,这时再使用testBox.trigger('customConsole')触发自定义事件,可以看到console只输出一个提示语,即成功移除后一个监听,至此事件机制所有功能正常工作。