不少DOM对象都有原生的事件支持,向div就有click、mouseover等事件,事件机制能够为类的设计带来很大的灵活性,相信.net程序员深有体会。随着web技术发展,使用JavaScript自定义对象愈发频繁,让本身建立的对象也有事件机制,经过事件对外通讯,可以极大提升开发效率。javascript
事件并非无关紧要,在某些需求下是必需的。以一个很简单的需求为例,在web开发中Dialog很常见,每一个Dialog都有一个关闭按钮,按钮对应Dialog的关闭方法,代码看起来大概是这样css
<!DOCTYPE html> <html> <head> <title>Test</title> <style type="text/css" > .dialog { position:fixed; width:300px; height:300px;z-index: 30;
这样在点击button的时候就能够弹出Dialog,点击关闭按钮的时候隐藏Dialog,看起来不错实现了需求,但总感受缺点儿什么,通常Dialog显示的时候页面还会弹出一层灰蒙蒙半透明的罩子,阻止页面其它地方的点击,Dialog隐藏的时候罩子去掉,页面又可以操做。加些代码添个罩子。html
在body顶部添加一个pagecoverjava
<div id="pageCover" class="pageCover"></div>
为其添加style程序员
.pageCover { width:100%; height:100%; position:absolute; z-index:10; background-color:#666; opacity:0.5; display:none; }
为了打开的时候显示page cover,须要修改openDialog方法web
function openDialog(){ var dlg=new Dialog('dlgTest'); document.getElementById('pageCover').style.display='block'; dlg.show(); }
效果很不错的样子,灰蒙蒙半透明的罩子在Dialog弹出后遮盖住了页面上的按钮,Dialog在其之上,这时候问题来了,关闭Dialog的时候page cover仍在,没有代码其隐藏它,看看打开的时候怎么显示的page cover,关闭的时候怎么隐藏行了! 还真不行,打开的代码是页面button按钮的事件处理程序本身定义的,在里面添加显示page cover的方法合情合理,可是关闭Dialog的方法是Dialog控件(虽然很简陋,远远算不上是控件)本身的逻辑,和页面无关,那修改Dialog的close方法能够吗?也不行!有两个缘由,首先Dialog在定义的时候并不知道page cover的存在,这两个控件之间没有什么耦合关系,若是把隐藏page cover逻辑写在Dialog的close方法内,那么dialog是依赖于page cover的,也就是说页面上若是没有page cover,dialog就会出错。并且Dialog在定义的时候,也不知道特定页面的page cover id,没有办法知道隐藏哪一个div,是否是在构造Dialog时把page cover id传入就能够了呢? 这样两个控件再也不有依赖关系,也可以经过id查找到page cover DIV了,可是若是用户有的页面须要弹出page cover,有的不须要怎么办?数组
这是就事件大显身手的时候了,修改一下dialog 对象和openDialog方法浏览器
function Dialog(id){ this.id=id; this.close_handler=null; var that=this; document.getElementById(id).children[0].onclick=function(){ that.close(); if(typeof that.close_handler=='function') { that.close_handler(); } } }
function openDialog(){ var dlg=new Dialog('dlgTest'); document.getElementById('pageCover').style.display='block'; dlg.close_handler=function(){ document.getElementById('pageCover').style.display='none'; } dlg.show(); }
在Dialog对象内部添加一个句柄,关闭按钮的click事件处理程序在调用close方法后判断该句柄是否为function,是的话就调用执行该句柄。在openDialog方法中,建立Dialog对象后对句柄赋值为一隐藏page cover方法,这样在关闭Dialog的时候就隐藏了page cover,同时没有形成两个控件之间的耦合。这一交互过程就是一个简单的 定义事件——绑定事件处理程序——触发事件的过程,DOM对象的事件,好比button的click事件也是相似原理。测试
上面举的小例子很简单,远远不及DOM自己事件精细,这种简单的事件处理有不少弊端this
1.没有共同性。若是在定义一个控件,还得写一套相似的结构处理
2.事件绑定有排斥性。只能绑定了一个close事件处理程序,绑定新的会覆盖以前绑定
3.封装不够完善。若是用户不知道有个 close_handler的句柄,就没有办法绑定该事件,只能去查源代码
逐个分析一下这几个弊端,弊端一很熟悉,使用过面向对象的同窗均可以轻易想到解决方法——继承;对于弊端二则能够提供一个容器(二维数组)来统一管理全部事件;弊端三的解决须要和弊端一结合在自定义的事件管理对象中添加统一接口用于添加/删除/触发事件
function EventTarget(){ this.handlers={}; } EventTarget.prototype={ constructor:EventTarget, addHandler:function(type,handler){ if(typeof this.handlers[type]=='undefined'){ this.handlers[type]=new Array(); } this.handlers[type].push(handler); }, removeHandler:function(type,handler){ if(this.handlers[type] instanceof Array){ var handlers=this.handlers[type]; for(var i=0,len=handlers.length;i<len;i++){ if(handler[i]==handler){ handlers.splice(i,1); break; } } } }, trigger:function(event){ if(!event.target){ event.target=this; } if(this.handlers[event.type] instanceof Array){ var handlers=this.handlers[event.type]; for(var i=0,len=handlers.length;i<len;i++){ handlers[i](event); } } } }
addHandler方法用于添加事件处理程序,removeHandler方法用于移除事件处理程序,全部的事件处理程序在属性handlers中统一存储管理。调用trigger方法触发一个事件,该方法接收一个至少包含type属性的对象做为参数,触发的时候会查找handlers属性中对应type的事件处理程序。写段代码测试一下。
function onClose(event){ alert('message:'+event.message); } var target=new EventTarget(); target.addHandler('close',onClose); //浏览器不能帮咱们建立事件对象了,本身建立一个 var event={ type:'close', message:'Page Cover closed!' }; target.trigger(event);
至此后连个弊端一解决,应用一下继承解决第一个弊端,下面是寄生式组合继承的核心代码,这种继承方式是目前公认的JavaScript最佳继承方式
function extend(subType,superType){ var prototype=Object(superType.prototype); prototype.constructor=subType; subType.prototype=prototype; }
最后写成的版本就是这样的
<!DOCTYPE html> <html> <head> <title>Test</title> <style type="text/css" > html,body { height:100%; width:100%; padding:0; margin:0; } .dialog { position:fixed; width:300px; height:300px; top:50%; left:50%; margin-top:-200px; margin-left:-200px; box-shadow:2px 2px 4px #ccc; background-color:#f1f1f1; z-index:30; display:none; } .dialog .title { font-size:16px; font-weight:bold; color:#fff; padding:4px; background-color:#404040; } .dialog .close { width:20px; height:20px; margin:3px; float:right; cursor:pointer; } .pageCover { width:100%; height:100%; position:absolute; z-index:10; background-color:#666; opacity:0.5; display:none; } </style> </head> <body> <div id="pageCover" class="pageCover"></div> <input type="button" value="Dialog Test" onclick="openDialog();"/> <div id="dlgTest" class="dialog"> <img class="close" alt="" src="images/close.png"> <div class="title">Dialog</div> <div class="content"> </div> </div> <script type="text/javascript"> function EventTarget(){ this.handlers={}; } EventTarget.prototype={ constructor:EventTarget, addHandler:function(type,handler){ if(typeof this.handlers[type]=='undefined'){ this.handlers[type]=new Array(); } this.handlers[type].push(handler); }, removeHandler:function(type,handler){ if(this.handlers[type] instanceof Array){ var handlers=this.handlers[type]; for(var i=0,len=handlers.length;i<len;i++){ if(handler[i]==handler){ handlers.splice(i,1); break; } } } }, trigger:function(event){ if(!event.target){ event.target=this; } if(this.handlers[event.type] instanceof Array){ var handlers=this.handlers[event.type]; for(var i=0,len=handlers.length;i<len;i++){ handlers[i](event); } } } } </script> <script type="text/javascript"> function extend(subType,superType){ var prototype=Object(superType.prototype); prototype.constructor=subType; subType.prototype=prototype; } </script> <script type="text/javascript"> function Dialog(id){ EventTarget.call(this) this.id=id; var that=this; document.getElementById(id).children[0].onclick=function(){ that.close(); } } extend(Dialog,EventTarget); Dialog.prototype.show=function(){ var dlg=document.getElementById(this.id); dlg.style.display='block'; dlg=null; } Dialog.prototype.close=function(){ var dlg=document.getElementById(this.id); dlg.style.display='none'; dlg=null; this.trigger({type:'close'}); } </script> <script type="text/javascript"> function openDialog(){ var dlg=new Dialog('dlgTest'); dlg.addHandler('close',function(){ document.getElementById('pageCover').style.display='none'; }); document.getElementById('pageCover').style.display='block'; dlg.show(); } </script> </body> <html>
这样解决了几个弊端看起来就完美多了,其实能够把打开Dialog显示page cover也写成相似关闭时事件的方式了。当代码中存在多个部分在特定时刻相互交互的状况下,自定义事件就很是有用了。若是每一个对象都有其它对象的引用,那么整个代码高度耦合,对象改动会影响其它对象,维护起来困难重重。自定义事件使对象解耦,功能隔绝,这样对象之间实现了高聚合。