事件設計概述

weixin_34219944發表於2007-01-31

事件設計概述
事件機制可以使程式邏輯更加符合現實世界,在JavaScript中很多物件都有自己的事件,例如按鈕就有onclick事件,下拉選單框就有onchange事件,通過這些事件可以方便程式設計。那麼對於自己定義的類,是否也可以實現事件機制呢?是的,通過事件機制,可以將類設計為獨立的模組,通過事件對外通訊,提高了程式的開發效率。本節就將詳細介紹JavaScript中的事件設計模式以及可能遇到的問題。

最簡單的事件設計模式
最簡單的一種模式是將一個類的方法成員定義為事件,這不需要任何特殊的語法,通常是一個空方法,例如:
function class1(){
      //建構函式
}
class1.prototype={
      show:function(){
            //show函式的實現
            this.onShow();  //觸發onShow事件
      },
      onShow:function(){}  //定義事件介面
}
上面的程式碼中,就定義了一個方法:show(),同時該方法中呼叫了onShow()方法,這個onShow()方法就是對外提供的事件介面,其用法如下:
//建立class1的例項
var obj=new class1();
//建立obj的onShow事件處理程式
obj.onShow=function(){
      alert("onshow event");
}
//呼叫obj的show方法
obj.show();

由此可見,obj.onShow方法在類的外部被定義,而在類的內部方法show()中被呼叫,這就實現了事件機制。
上述方法很簡單,實際的開發中常用來解決一些簡單的事件功能。說它簡單,因為它有以下兩個缺點:
? 不能夠給事件處理程式傳遞引數,因為是在show()這個內部方法中呼叫事件處理程式的,無法知道外部的引數;
? 每個事件介面僅能夠繫結一個事件處理程式,而內部方法則可以使用attachEvent或者addEventListener方法繫結多個處理程式。
在下面兩小節將著重解決這個問題。

給事件處理程式傳遞引數
給事件處理程式傳遞引數不僅是自定義事件中存在的問題,也是系統內部物件的事件機制中存在的問題,因為事件機制僅傳遞一個函式的名稱,不帶有任何引數的資訊,所以無法傳遞引數進去。例如:
//定義類class1
function class1(){
      //建構函式
}
class1.prototype={
      show:function(){
            //show函式的實現
            this.onShow();  //觸發onShow事件
      },
      onShow:function(){}  //定義事件介面
}
//建立class1的例項
var obj=new class1();
//建立obj的onShow事件處理程式
function objOnShow(userName){
       alert("hello,"+userName);
}
//定義變數userName
var userName="jack";
//繫結obj的onShow事件
obj.onShow=objOnShow;  //無法將userName這個變數傳遞進去
//呼叫obj的show方法
obj.show();
注意上面的obj.onShow=objOnShow事件繫結語句,不能為了傳遞userName變數進去而寫成:
obj.onShow=objOnShow(userName);
或者:
obj.onShow="objOnShow(userName)";
前者是將objOnShow(userName)的執行結果賦給了obj.onShow,而後者是將字串“objOnShow(userName)”賦給了obj.onShow。
要解決這個問題,可以從相反的思路去考慮,不考慮怎麼把引數傳進去,而是考慮如何構建一個無需引數的事件處理程式,該程式是根據有引數的事件處理程式建立的,是一個外層的封裝。現在自定義一個通用的函式來實現這種功能:
//將有引數的函式封裝為無引數的函式
function createFunction(obj,strFunc){
      var args=[];      //定義args用於儲存傳遞給事件處理程式的引數
      if(!obj)obj=window;     //如果是全域性函式則obj=window;
      //得到傳遞給事件處理程式的引數
      for(var i=2;i<arguments.length;i++)args.push(arguments[i]);
      //用無引數函式封裝事件處理程式的呼叫
      return function(){
            obj[strFunc].apply(obj,args); //將引數傳遞給指定的事件處理程式
      }
}
該方法將一個有引數的函式封裝為一個無引數的函式,不僅對全域性函式適用,作為物件方法存在的函式同樣適用。該方法首先接收兩個引數:obj和strFunc,obj表示事件處理程式所在的物件;strFunc表示事件處理程式的名稱。除此以外,程式中還利用arguments物件處理第二個引數以後的隱式引數,即未定義形參的引數,並在呼叫事件處理程式時將這些引數傳遞進去。例如一個事件處理程式是:
someObject.eventHandler=function(_arg1,_arg2){
     //事件處理程式碼
}
應該呼叫:
createFunction(someObject,"eventHandler",arg1,arg2);
這就返回一個無引數的函式,在返回的函式中已經包括了傳遞進去的引數。如果是全域性函式作為事件處理程式,事實上它是window物件的一個方法,所以可以傳遞window物件作為obj引數,為了更清晰一點,也可以指定obj為null,createFunction函式內部會自動認為該函式是全域性函式,從而自動把obj賦值為window。下面來看應用的例子:
<script language="JavaScript" type="text/javascript">
<!--
//將有引數的函式封裝為無引數的函式
function createFunction(obj,strFunc){
      var args=[];
      if(!obj)obj=window;
      for(var i=2;i<arguments.length;i++)args.push(arguments[i]);
      return function(){
            obj[strFunc].apply(obj,args);
      }
}
//定義類class1
function class1(){
      //建構函式
}
class1.prototype={
      show:function(){
            //show函式的實現
            this.onShow();  //觸發onShow事件
      },
      onShow:function(){} //定義事件介面
}
//建立class1的例項
var obj=new class1();
//建立obj的onShow事件處理程式
function objOnShow(userName){
      alert("hello,"+userName);
}
//定義變數userName
var userName="jack";
//繫結obj的onShow事件
obj.onShow=createFunction(null,"objOnShow",userName);
//呼叫obj的show方法
obj.show();
//-->
</script>
在這段程式碼中,就將變數userName作為引數傳遞給了objOnShow事件處理程式。事實上,obj.onShow得到的事件處理程式並不是objOnShow,而是由createFunction返回的一個無參函式。
通過createFunction封裝,就可以用一種通用的方案實現引數傳遞了。這不僅適用於自定義的事件,也適用於系統提供的事件,其原理是完全相同的。


使自定義事件支援多繫結
可以用attachEvent或者addEventListener方法來實現多個事件處理程式的同時繫結,不會互相沖突,而自定義事件怎樣來實現多訂閱呢?下面介紹這種實現。要實現多訂閱,必定需要一個機制用於儲存繫結的多個事件處理程式,在事件發生時同時呼叫這些事件處理程式。從而達到多訂閱的效果,其實現如下:
<script language="JavaScript" type="text/javascript">
<!--
//定義類class1
function class1(){
      //建構函式
}
//定義類成員
class1.prototype={
      show:function(){
           //show的程式碼
           //...

           //如果有事件繫結則迴圈onshow陣列,觸發該事件
           if(this.onshow){
                  for(var i=0;i<this.onshow.length;i++){
                        this.onshow[i](); //呼叫事件處理程式
                  }
           }
      },
      attachOnShow:function(_eHandler){
            if(!this.onshow)this.onshow=[]; //用陣列儲存繫結的事件處理程式引用
            this.onshow.push(_eHandler);
      }
}
var obj=new class1();
//事件處理程式1
function onShow1(){
      alert(1);
}
//事件處理程式2
function onShow2(){
      alert(2);
}
//繫結兩個事件處理程式
obj.attachOnShow(onShow1);
obj.attachOnShow(onShow2);
//呼叫show,觸發onshow事件
obj.show();
//-->
</script>
從程式碼的執行結果可以看到,繫結的兩個事件處理程式都得到了正確的執行。如果要繫結有引數的事件處理程式,只需加上createFunction方法即可,在上一節有過描述。
這種機制基本上說明了處理多事件處理程式的基本思想,但還有改進的餘地。例如如果類有多個事件,可以定義一個類似於attachEvent的方法,用於統一處理事件繫結。在新增了事件繫結後如果想刪除,還可以定義一個detachEvent方法用於取消繫結。這些實現的基本思想都是對陣列的操作。

相關文章