JavaScript自定義事件

不少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;
                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 >
   
    < 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 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 >
   
    < script type ="text/javascript" >
        function openDialog(){
          
var dlg = new Dialog( ' dlgTest ' );
            dlg.show();
        }
  
</ script >
    </ body >
< html >

 

這樣在點擊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();
        }

 

image

效果很不錯的樣子,灰濛濛半透明的罩子在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也寫成相似關閉時事件的方式了。當代碼中存在多個部分在特定時刻相互交互的狀況下,自定義事件就很是有用了。若是每一個對象都有其它對象的引用,那麼整個代碼高度耦合,對象改動會影響其它對象,維護起來困難重重。自定義事件使對象解耦,功能隔絕,這樣對象之間實現了高聚合。

相關文章
相關標籤/搜索