JavaScript自定義事件介紹
很多DOM對象都有原生的事件支持,向div就有click、mouseover等事件,事件機制可以為類的設(shè)計帶來很大的靈活性,相信.net程序員深有體會。隨著web技術(shù)發(fā)展,使用JavaScript自定義對象愈發(fā)頻繁,讓自己創(chuàng)建的對象也有事件機制,通過事件對外通信,能夠極大提高開發(fā)效率。
簡單的事件需求
事件并不是可有可無,在某些需求下是必需的。以一個很簡單的需求為例,在web開發(fā)中Dialog很常見,每個Dialog都有一個關(guān)閉按鈕,按鈕對應Dialog的關(guān)閉方法,代碼看起來大概是這樣
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style type="text/css" >
.dialog
{
position:fixed;
width:300px;
height:300px; z-index:30;
top:50%; left:50%;
margin-top:-200px; margin-left:-200px;
box-shadow:2px 2px 4px #ccc;
background-color:#f1f1f1;
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;
}
</style>
</head>
<body>
<inputtype="button" value="Dialog Test" onclick="openDialog();"/>
<divid="dlgTest" class="dialog">
<imgclass="close" alt="" src="images/close.png">
<divclass="title">Dialog</div>
<divclass="content">
</div>
</div>
<scripttype="text/javascript">
function Dialog(id){
this.id=id;
var that=this;
document.getElementById(id).children[0].onclick=function(){
that.close();
}
}
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;
}
</script>
<scripttype="text/javascript">
function openDialog(){
var dlg=new Dialog('dlgTest');
dlg.show();
}
</script>
</body>
<html>
這樣在點擊button的時候就可以彈出Dialog,點擊關(guān)閉按鈕的時候隱藏Dialog,看起來不錯實現(xiàn)了需求,但總感覺缺點兒什么,一般Dialog顯示的時候頁面還會彈出一層灰蒙蒙半透明的罩子,阻止頁面其它地方的點擊,Dialog隱藏的時候罩子去掉,頁面又能夠操作。加些代碼添個罩子。
在body頂部添加一個pagecover
<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方法
function openDialog(){
var dlg=new Dialog('dlgTest');
document.getElementById('pageCover').style.display='block';
dlg.show();
}

效果很不錯的樣子,灰蒙蒙半透明的罩子在Dialog彈出后遮蓋住了頁面上的按鈕,Dialog在其之上,這時候問題來了,關(guān)閉Dialog的時候page cover仍在,沒有代碼其隱藏它,看看打開的時候怎么顯示的page cover,關(guān)閉的時候怎么隱藏行了! 還真不行,打開的代碼是頁面button按鈕的事件處理程序自己定義的,在里面添加顯示page cover的方法合情合理,但是關(guān)閉Dialog的方法是Dialog控件(雖然很簡陋,遠遠算不上是控件)自己的邏輯,和頁面無關(guān),那修改Dialog的close方法可以嗎?也不行!有兩個原因,首先Dialog在定義的時候并不知道page cover的存在,這兩個控件之間沒有什么耦合關(guān)系,如果把隱藏page cover邏輯寫在Dialog的close方法內(nèi),那么dialog是依賴于page cover的,也就是說頁面上如果沒有page cover,dialog就會出錯。而且Dialog在定義的時候,也不知道特定頁面的page cover id,沒有辦法知道隱藏哪個div,是不是在構(gòu)造Dialog時把page cover id傳入就可以了呢? 這樣兩個控件不再有依賴關(guān)系,也能夠通過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對象內(nèi)部添加一個句柄,關(guān)閉按鈕的click事件處理程序在調(diào)用close方法后判斷該句柄是否為function,是的話就調(diào)用執(zhí)行該句柄。在openDialog方法中,創(chuàng)建Dialog對象后對句柄賦值為一隱藏page cover方法,這樣在關(guān)閉Dialog的時候就隱藏了page cover,同時沒有造成兩個控件之間的耦合。這一交互過程就是一個簡單的 定義事件——綁定事件處理程序——觸發(fā)事件的過程,DOM對象的事件,比如button的click事件也是類似原理。
高級一點的自定義事件
上面舉的小例子很簡單,遠遠不及DOM本身事件精細,這種簡單的事件處理有很多弊端
1.沒有共同性。如果在定義一個控件,還得寫一套類似的結(jié)構(gòu)處理
2.事件綁定有排斥性。只能綁定了一個close事件處理程序,綁定新的會覆蓋之前綁定
3.封裝不夠完善。如果用戶不知道有個 close_handler的句柄,就沒有辦法綁定該事件,只能去查源代碼
逐個分析一下這幾個弊端,弊端一很熟悉,使用過面向?qū)ο蟮耐瑢W都可以輕易想到解決方法——繼承;對于弊端二則可以提供一個容器(二維數(shù)組)來統(tǒng)一管理所有事件;弊端三的解決需要和弊端一結(jié)合在自定義的事件管理對象中添加統(tǒng)一接口用于添加/刪除/觸發(fā)事件
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中統(tǒng)一存儲管理。調(diào)用trigger方法觸發(fā)一個事件,該方法接收一個至少包含type屬性的對象作為參數(shù),觸發(fā)的時候會查找handlers屬性中對應type的事件處理程序。寫段代碼測試一下。
function onClose(event){
alert('message:'+event.message);
}
var target=new EventTarget();
target.addHandler('close',onClose);
//瀏覽器不能幫我們創(chuàng)建事件對象了,自己創(chuàng)建一個
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也寫成類似關(guān)閉時事件的方式了。當代碼中存在多個部分在特定時刻相互交互的情況下,自定義事件就非常有用了。如果每個對象都有其它對象的引用,那么整個代碼高度耦合,對象改動會影響其它對象,維護起來困難重重。自定義事件使對象解耦,功能隔絕,這樣對象之間實現(xiàn)了高聚合。
相關(guān)文章
swiper+echarts實現(xiàn)多個儀表盤左右滾動效果
這篇文章主要為大家詳細介紹了swiper+echarts實現(xiàn)多個儀表盤左右滾動效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-06-06
JS實現(xiàn)經(jīng)典的中國地區(qū)三級聯(lián)動下拉菜單功能實例【測試可用】
這篇文章主要介紹了JS實現(xiàn)經(jīng)典的中國地區(qū)三級聯(lián)動下拉菜單功能,結(jié)合完整實例形式分析了javascript基于事件響應實現(xiàn)頁面元素動態(tài)變換的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
加載遠程圖片時,經(jīng)常因為緩存而得不到更新的解決方法(分享)
本篇文章是對加載遠程圖片時,經(jīng)常因為緩存而得不到更新的解決方法進行了詳細的分析介紹,需要的朋友參考下2013-06-06
JavaScript本地存儲與會話存儲的實現(xiàn)介紹
本地存儲和會話存儲是比較常用的方法,你知道兩者的區(qū)別嗎,本文詳細的介紹了JavaScript中本地存儲(LocalStorage)和會話存儲(SessionStorage)的使用,具有一定的參考價值,感興趣的可以了解一下2022-08-08

