1. 程式人生 > >第13章 事件

第13章 事件

以及 例子 依次 屬於 size string 接受 type屬性 log

JavaScript 與HTML 之間的交互是通過事件實現的。事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。可以使用偵聽器(或處理程序)來預訂事件,以便事件發生時執行相應的代碼。這種在傳統軟件工程中被稱為觀察員模式的模型,支持頁面的行為(JavaScript 代碼)與頁
面的外觀(HTML 和CSS 代碼)之間的松散耦合。

13.1 事件流

事件流描述的是從頁面中接收事件的順序。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕獲流。

13.1.1 事件冒泡

IE 的事件流叫做事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,然後逐級向上傳播到較為不具體的節點(文檔)

<!DOCTYPE html>
<html>
  <head>
    <title>Event Bubbling Example</title>
  </head>
  <body>
    <div id="myDiv">Click Me</div>
  </body>
</html>

如果你單擊了頁面中的<div>元素,那麽這個click 事件會按照如下順序傳播:

(1) <div>
(2) <body>
(3) <html>
(4) document

也就是說,click 事件首先在<div>元素上發生,而這個元素就是我們單擊的元素。然後,click事件沿DOM樹向上傳播,在每一級節點上都會發生,直至傳播到document 對象。

事件冒泡過程:

技術分享

所有現代瀏覽器都支持事件冒泡,但在具體實現上還是有一些差別。IE5.5 及更早版本中的事件冒泡會跳過<html>元素(從<body>直接跳到document)。IE9、Firefox、Chrome 和Safari 則將事件一直冒泡到window 對象。

13.1.2 事件捕獲

Netscape Communicator 團隊提出的另一種事件流叫做事件捕獲(event capturing)。事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於在事件到達預定目標之前捕獲它。如果仍以前面的HTML 頁面作為演示事件捕獲的例子,那麽單擊<div>元素就會以下列順序觸發click 事件。

(1)document
(2)<html>
(3)<body>
(4)<div>

在事件捕獲過程中,document 對象首先接收到click 事件,然後事件沿DOM 樹依次向下,一直傳播到事件的實際目標,即<div>元素。

事件捕獲的過程:

技術分享

雖然事件捕獲是Netscape Communicator 唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox 目前也都支持這種事件流模型。盡管“DOM2 級事件”規範要求事件應該從document 對象開始傳播,但這些瀏覽器都是從window 對象開始捕獲事件的。

由於老版本的瀏覽器不支持,因此很少有人使用事件捕獲。

13.1.3 DOM事件流

“DOM2級事件”規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,為截獲事件提供了機會。然後是實際的目標接收到事件。最後一個階段是冒泡階段,可以在這個階段對事件做出響應。以前面簡單的HTML 頁面為例,單擊<div>元素會按照圖所示順序觸發事件。

技術分享

在DOM 事件流中,實際的目標(<div>元素)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件從document 到<html>再到<body>後就停止了。下一個階段是“處於目標”階段,於是事件在<div>上發生,並在事件處理(後面將會討論這個概念)中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播回文檔。

多數支持DOM 事件流的瀏覽器都實現了一種特定的行為;即使“DOM2 級事件”規範明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Chrome、Firefox 和Opera 9.5 及更高版本都會在捕獲階段觸發事件對象上的事件。結果,就是有兩個機會在目標對象上面操作事件。

IE9、Opera、Firefox、Chrome 和Safari 都支持DOM 事件流;IE8 及更早版本不支持DOM 事件流。

13.2 事件處理程序

事件就是用戶或瀏覽器自身執行的某種動作。諸如click、load 和mouseover,都是事件的名字。而響應某個事件的函數就叫做事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,因此click 事件的事件處理程序就是onclick,load 事件的事件處理程序就是onload。為事件指定處理程序的方式有好幾種。

13.2.1 HTML事件處理程序

某個元素支持的每種事件,都可以使用一個與相應事件處理程序同名的HTML 特性來指定。這個特性的值應該是能夠執行的JavaScript 代碼。例如,要在按鈕被單擊時執行一些JavaScript,可以像下面這樣編寫代碼:

<input type="button" value="Click Me" onclick="alert(‘Clicked‘)" />

由於這個值是JavaScript,因此不能在其中使用未經轉義的HTML 語法字符,例如和號(&)、雙引號("")、小於號(<)或大於號(>)。為了避免使用HTML 實體,這裏使用了單引號。如果想要使用雙引號,那麽就要將代碼改寫成如下所示:

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

在HTML 中定義的事件處理程序可以包含要執行的具體動作,也可以調用在頁面其他地方定義的腳本,如下面的例子所示:

<script type="text/javascript">
  function showMessage(){
    alert("Hello world!");
  }
</script>
<input type="button" value="Click Me" onclick="showMessage()" />

單擊按鈕就會調用showMessage()函數。這個函數是在一個獨立的<script>元素中定義的,當然也可以被包含在一個外部文件中。事件處理程序中的代碼在執行時,有權訪問全局作用域中的任何代碼。

這樣指定事件處理程序具有一些獨到之處。首先,這樣會創建一個封裝著元素屬性值的函數。這個函數中有一個局部變量event,也就是事件對象。

<!-- 輸出 "click" -->
<input type="button" value="Click Me" onclick="alert(event.type)">

通過event 變量,可以直接訪問事件對象,你不用自己定義它,也不用從函數的參數列表中讀取。在這個函數內部,this 值等於事件的目標元素。

<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">

關於這個動態創建的函數,另一個有意思的地方是它擴展作用域的方式。在這個函數內部,可以像訪問局部變量一樣訪問document 及該元素本身的成員。這個函數使用with 像下面這樣擴展作用域:

function(){
  with(document){
    with(this){
    //元素屬性值
    }
  }
}

如此一來,事件處理程序要訪問自己的屬性就簡單多了。下面這行代碼與前面的例子效果相同:

<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(value)">

如果當前元素是一個表單輸入元素,則作用域中還會包含訪問表單元素(父元素)的入口,這個函數就變成了如下所示:

function(){
  with(document){
    with(this.form){
      with(this){
        //元素屬性值
      }
    }
  }
}

實際上,這樣擴展作用域的方式,無非就是想讓事件處理程序無需引用表單元素就能訪問其他表單字段。例如:

<form method="post">
  <input type="text" name="username" value="">
  <input type="button" value="Echo Username" onclick="alert(username.value)">
</form>

在HTML 中指定事件處理程序有兩個缺點。首先,存在一個時差問題。因為用戶可能會在HTML 元素一出現在頁面上就觸發相應的事件,但當時的事件處理程序有可能尚不具備執行條件,因此很多HTML 事件處理程序都會被封裝在一個try-catch 塊中,以便錯誤不會浮出水面,如下面的例子所示:

<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">

這樣,如果在showMessage()函數有定義之前單擊了按鈕,用戶將不會看到JavaScript 錯誤,因為在瀏覽器有機會處理錯誤之前,錯誤就被捕獲了。

另一個缺點是,這樣擴展事件處理程序的作用域鏈在不同瀏覽器中會導致不同結果。不同JavaScript引擎遵循的標識符解析規則略有差異,很可能會在訪問非限定對象成員時出錯。

通過HTML 指定事件處理程序的最後一個缺點是HTML 與JavaScript 代碼緊密耦合。如果要更換事件處理程序,就要改動兩個地方:HTML 代碼和JavaScript 代碼。而這正是許多開發人員摒棄HTML 事件處理程序,轉而使用JavaScript 指定事件處理程序的原因所在。

13.2.2 DOM0 級事件處理程序

通過JavaScript 指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這種為事件處理程序賦值的方法是在第四代Web 瀏覽器中出現的,而且至今仍然為所有現代瀏覽器所支持。原因一是簡單,二是具有跨瀏覽器的優勢。要使用JavaScript 指定事件處理程序,首先必須取得一個要操作的對象的引用。

每個元素(包括window 和document)都有自己的事件處理程序屬性,這些屬性通常全部小寫,例如onclick。將這種屬性的值設置為一個函數,就可以指定事件處理程序,如下所示:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert("Clicked");
};

使用DOM0 級方法指定的事件處理程序被認為是元素的方法。因此,這時候的事件處理程序是在元素的作用域中運行;換句話說,程序中的this 引用當前元素。來看一個例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert(this.id); //"myBtn"
};

單擊按鈕顯示的是元素的ID,這個ID 是通過this.id 取得的。不僅僅是ID,實際上可以在事件處理程序中通過this 訪問元素的任何屬性和方法。以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。

也可以刪除通過DOM0 級方法指定的事件處理程序,只要像下面這樣將事件處理程序屬性的值設置為null 即可:

btn.onclick = null; //刪除事件處理程序

將事件處理程序設置為null 之後,再單擊按鈕將不會有任何動作發生。

13.2.3 DOM2級事件處理程序

“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程序的操作:addEventListener()和emoveEventListener()。所有DOM節點中都包含這兩個方法,並且它們都接受3 個參數:要處理的事件名、作為事件處理程序的函數和一個布爾值。最後這個布爾值參數如果是true,表示在捕獲階段調用事件處理程序;如果是false,表示在冒泡階段調用事件處理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
  alert(this.id);
}, false);

上面的代碼為一個按鈕添加了onclick 事件處理程序,而且該事件會在冒泡階段被觸發(因為最後一個參數是false)。與DOM0 級方法一樣,這裏添加的事件處理程序也是在其依附的元素的作用域中運行。使用DOM2 級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id);
}, false);
btn.addEventListener("click", function(){
    alert("Hello world!");
}, false);

這裏為按鈕添加了兩個事件處理程序。這兩個事件處理程序會按照添加它們的順序觸發,因此首先會顯示元素的ID,其次會顯示"Hello world!"消息。

通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移除時傳入的參數與添加處理程序時使用的參數相同。這也意味著通過addEventListener()添加的匿名函數將無法移除,如下面的例子所示。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
  alert(this.id);
}, false);
//這裏省略了其他代碼
btn.removeEventListener("click", function(){ //沒有用!
  alert(this.id);
}, false);

在這個例子中,我們使用addEventListener()添加了一個事件處理程序。雖然調用removeEventListener()時看似使用了相同的參數,但實際上,第二個參數與傳入addEventListener()中的那一個是完全不同的函數。而傳入removeEventListener()中的事件處理程序函數必須與傳入addEventListener()中的相同,如下面的例子所示。

var btn = document.getElementById("myBtn");
var handler = function(){
  alert(this.id);
};
btn.addEventListener("click", handler, false);
//這裏省略了其他代碼
btn.removeEventListener("click", handler, false); //有效!

重寫後的這個例子沒有問題,是因為在addEventListener()和removeEventListener()中使用了相同的函數。

大多數情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。

13.2.4 IE事件處理程序

IE 實現了與DOM 中類似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。由於IE8 及更早版本只支持事件冒泡,所以通過attachEvent()添加的事件處理程序都會被添加到冒泡階段。

要使用attachEvent()為按鈕添加一個事件處理程序,可以使用以下代碼。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("Clicked");
});

註意,attachEvent()的第一個參數是"onclick",而非DOM 的addEventListener()方法中的"click"。

在IE 中使用attachEvent()與使用DOM0 級方法的主要區別在於事件處理程序的作用域。在使用DOM0 級方法的情況下,事件處理程序會在其所屬元素的作用域內運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域中運行,因此this 等於window。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
  alert(this === window); //true
});

與addEventListener()類似,attachEvent()方法也可以用來為一個元素添加多個事件處理程序。來看下面的例子。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
  alert("Clicked");
});
btn.attachEvent("onclick", function(){
  alert("Hello world!");
});

這裏調用了兩次attachEvent(),為同一個按鈕添加了兩個不同的事件處理程序。不過,與DOM方法不同的是,這些事件處理程序不是以添加它們的順序執行,而是以相反的順序被觸發。單擊這個例子中的按鈕,首先看到的是"Hello world!",然後才是"Clicked"。

使用attachEvent()添加的事件可以通過detachEvent()來移除,條件是必須提供相同的參數。與DOM 方法一樣,這也意味著添加的匿名函數將不能被移除。不過,只要能夠將對相同函數的引用傳給detachEvent(),就可以移除相應的事件處理程序。例如:

var btn = document.getElementById("myBtn");
var handler = function(){
  alert("Clicked");
};
btn.attachEvent("onclick", handler);
//這裏省略了其他代碼
btn.detachEvent("onclick", handler);

13.2.5 跨瀏覽器的事件處理程序

為了以跨瀏覽器的方式處理事件,不少開發人員會使用能夠隔離瀏覽器差異的JavaScript 庫,還有一些開發人員會自己開發最合適的事件處理的方法。自己編寫代碼其實也不難,只要恰當地使用能力檢測即可。要保證處理事件的代碼能在大多數瀏覽器下一致地運行,只需關註冒泡階段。

第一個要創建的方法是addHandler(),它的職責是視情況分別使用DOM0 級方法、DOM2 級方法或IE 方法來添加事件。這個方法屬於一個名叫EventUtil 的對象,本書將使用這個對象來處理瀏覽器間的差異。addHandler()方法接受3 個參數:要操作的元素、事件名稱和事件處理程序函數。

與addHandler()對應的方法是removeHandler(),它也接受相同的參數。這個方法的職責是移除之前添加的事件處理程序——無論該事件處理程序是采取什麽方式添加到元素中的,如果其他方法無效,默認采用DOM0 級方法。

EventUtil 的用法如下所示。

var EventUtil = {
  addHandler: function(element, type, handler){
    if (element.addEventListener){
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent){
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  },
  removeHandler: function(element, type, handler){
    if (element.removeEventListener){
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent){
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  }
};

這兩個方法首先都會檢測傳入的元素中是否存在DOM2 級方法。如果存在DOM2 級方法,則使用該方法:傳入事件類型、事件處理程序函數和第三個參數false(表示冒泡階段)。如果存在的是IE 的方法,則采取第二種方案。註意,為了在IE8 及更早版本中運行,此時的事件類型必須加上"on"前綴。最後一種可能就是使用DOM0 級方法(在現代瀏覽器中,應該不會執行這裏的代碼)。此時,我們使用的是方括號語法來將屬性名指定為事件處理程序,或者將屬性設置為null。

可以像下面這樣使用EventUtil 對象:

var btn = document.getElementById("myBtn");
var handler = function(){
  alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//這裏省略了其他代碼
EventUtil.removeHandler(btn, "click", handler);

13.3 事件對象

在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含著所有與事件有關的信息。包括導致事件的元素、事件的類型以及其他與特定事件相關的信息。例如,鼠標操作導致的事件對象中,會包含鼠標位置的信息,而鍵盤操作導致的事件對象中,會包含與按下的鍵有關的信息。所有瀏覽器都支持event 對象,但支持方式不同。

13.3.1 DOM中的事件對象

兼容DOM 的瀏覽器會將一個event 對象傳入到事件處理程序中。無論指定事件處理程序時使用什麽方法(DOM0 級或DOM2 級),都會傳入event 對象。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
  alert(event.type); //"click"
}, false);

這個例子中的兩個事件處理程序都會彈出一個警告框,顯示由event.type 屬性表示的事件類型。這個屬性始終都會包含被觸發的事件類型, 例如"click" ( 與傳入addEventListener() 和removeEventListener()中的事件類型一致)。

在通過HTML 特性指定事件處理程序時,變量event 中保存著event 對象。請看下面的例子。

<input type="button" value="Click Me" onclick="alert(event.type)"/>

以這種方式提供event 對象,可以讓HTML 特性事件處理程序與JavaScript 函數執行相同的操作。

event 對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方法也不一樣。不過,所有事件都會有下表列出的成員。

屬性/方法 類型 讀/寫 說明
bubbles Boolean 只讀 表明事件是否冒泡
cancelable Boolean 只讀 表明是否可以取消事件的默認行為
currentTarget Element 只讀 其事件處理程序當前正在處理事件的那個元素
defaultPrevented Boolean 只讀

為true 表示已經調用了preventDefault()(DOM3級事件中新增)

detail Integer 只讀 與事件相關的細節信息
eventPhase Integer 只讀

調用事件處理程序的階段:1表示捕獲階段,2表示“處於目標”,
3表示冒泡階段

preventDefault() Function 只讀

取消事件的默認行為。如果cancelable是true,則可以使用這個方法

stopImmediatePropagation() Function 只讀

取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用
(DOM3級事件中新增)

stopPropagation() Function 只讀

取消事件的進一步捕獲或冒泡。如果bubbles為true,則可以使用這個方法

target Element 只讀 事件的目標
trusted Boolean 只讀

為true表示事件是瀏覽器生成的。為false表示事件是由開發人員通過
JavaScript 創建的(DOM3級事件中新增)

type String 只讀 被觸發的事件的類型
view AbstractView 只讀

與事件關聯的抽象視圖。等同於發生事件的window對象

在事件處理程序內部,對象this 始終等於currentTarget 的值,而target 則只包含事件的實際目標。如果直接將事件處理程序指定給了目標元素,則this、currentTarget 和target 包含相同的值。來看下面的例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.currentTarget === this); //true
  alert(event.target === this); //true
};

這個例子檢測了currentTarget 和target 與this 的值。由於click 事件的目標是按鈕,因此這三個值是相等的。如果事件處理程序存在於按鈕的父節點中(例如document.body),那麽這些值是不相同的。再看下面的例子。

document.body.onclick = function(event){
  alert(event.currentTarget === document.body); //true
  alert(this === document.body); //true
  alert(event.target === document.getElementById("myBtn")); //true
};

當單擊這個例子中的按鈕時,this 和currentTarget 都等於document.body,因為事件處理程序是註冊到這個元素上的。然而,target 元素卻等於按鈕元素,因為它是click 事件真正的目標。由於按鈕上並沒有註冊事件處理程序,結果click 事件就冒泡到了document.body,在那裏事件才得到了處理。

在需要通過一個函數處理多個事件時,可以使用type 屬性。例如:

var btn = document.getElementById("myBtn");
var handler = function(event){
  switch(event.type){
    case "click":
      alert("Clicked");
      break;
    case "mouseover":
      event.target.style.backgroundColor = "red";
      break;
    case "mouseout":
      event.target.style.backgroundColor = "";
      break;
  }
};
btn.onclick = handler;
btn.onmouseover = handler;
btn. onmouseout = handler;

這個例子定義了一個名為handler 的函數,用於處理3 種事件:click、mouseover 和mouseout。當單擊按鈕時,會出現一個與前面例子中一樣的警告框。當按鈕移動到按鈕上面時,背景顏色應該會變成紅色,而當鼠標移動出按鈕的範圍時,背景顏色應該會恢復為默認值。這裏通過檢測event.type
屬性,讓函數能夠確定發生了什麽事件,並執行相應的操作。

要阻止特定事件的默認行為,可以使用preventDefault()方法。例如,鏈接的默認行為就是在被單擊時會導航到其href 特性指定的URL。如果你想阻止鏈接導航這一默認行為,那麽通過鏈接的onclick 事件處理程序可以取消它,如下面的例子所示。

var link = document.getElementById("myLink");
link.onclick = function(event){
  event.preventDefault();
};

只有cancelable 屬性設置為true 的事件,才可以使用preventDefault()來取消其默認行為。

另外,stopPropagation()方法用於立即停止事件在DOM 層次中的傳播,即取消進一步的事件捕獲或冒泡。例如,直接添加到一個按鈕的事件處理程序可以調用stopPropagation(),從而避免觸發註冊在document.body 上面的事件處理程序,如下面的例子所示。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert("Clicked");
  event.stopPropagation();
};
document.body.onclick = function(event){
  alert("Body clicked");
};

對於這個例子而言,如果不調用stopPropagation(),就會在單擊按鈕時出現兩個警告框。可是,由於click 事件根本不會傳播到document.body,因此就不會觸發註冊在這個元素上的onclick 事件處理程序。

事件對象的eventPhase 屬性,可以用來確定事件當前正位於事件流的哪個階段。如果是在捕獲階段調用的事件處理程序,那麽eventPhase 等於1;如果事件處理程序處於目標對象上,則eventPhase 等於2;如果是在冒泡階段調用的事件處理程序,eventPhase 等於3。這裏要註意的是,盡管
“處於目標”發生在冒泡階段,但eventPhase 仍然一直等於2。來看下面的例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.eventPhase); //2
};
document.body.addEventListener("click", function(event){
  alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event){
  alert(event.eventPhase); //3
};

當單擊這個例子中的按鈕時,首先執行的事件處理程序是在捕獲階段觸發的添加到document.body中的那一個,結果會彈出一個警告框顯示表示eventPhase 的1。接著,會觸發在按鈕上註冊的事件處理程序,此時的eventPhase 值為2。最後一個被觸發的事件處理程序,是在冒泡階段執行的添加到document.body 上的那一個,顯示eventPhase 的值為3。而當eventPhase 等於2 時,this、target和currentTarget 始終都是相等的。

只有在事件處理程序執行期間,event 對象才會存在;一旦事件處理程序執行完成,event 對象就會被銷毀。

13.3.2 IE中的事件對象

與訪問DOM中的event 對象不同,要訪問IE 中的event 對象有幾種不同的方式,取決於指定事件處理程序的方法。在使用DOM0 級方法添加事件處理程序時,event 對象作為window 對象的一個屬性存在。來看下面的例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  var event = window.event;
  alert(event.type); //"click"
};

在此,我們通過window.event 取得了event 對象,並檢測了被觸發事件的類型(IE 中的type屬性與DOM中的type 屬性是相同的)。可是,如果事件處理程序是使用attachEvent()添加的,那麽就會有一個event 對象作為參數被傳入事件處理程序函數中,如下所示。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event){
  alert(event.type); //"click"
});

在像這樣使用attachEvent()的情況下,也可以通過window 對象來訪問event 對象,就像使用DOM0 級方法時一樣。不過為方便起見,同一個對象也會作為參數傳遞。

如果是通過HTML特性指定的事件處理程序,那麽還可以通過一個名叫event 的變量來訪問event對象(與DOM 中的事件模型相同)。再看一個例子。

<input type="button" value="Click Me" onclick="alert(event.type)">

IE 的event 對象同樣也包含與創建它的事件相關的屬性和方法。其中很多屬性和方法都有對應的或者相關的DOM屬性和方法。與DOM的event 對象一樣,這些屬性和方法也會因為事件類型的不同而不同,但所有事件對象都會包含下表所列的屬性和方法。

屬性/方法 類型 讀/寫 說明
cancelBubble Boolean 讀/寫

默認值為false,但將其設置為true就可以取消事件冒泡(與DOM中
的stopPropagation()方法的作用相同)

returnValue Boolean 讀/寫

默認值為true,但將其設置為false就可以取消事件的默認行為(與
DOM中的preventDefault()方法的作用相同)

srcElement Element 只讀 事件的目標(與DOM中的target屬性相同)
type String 只讀 被觸發的事件的類型

因為事件處理程序的作用域是根據指定它的方式來確定的,所以不能認為this 會始終等於事件目標。故而,最好還是使用event.srcElement 比較保險。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert(window.event.srcElement === this); //true
};
btn.attachEvent("onclick", function(event){
  alert(event.srcElement === this); //false
});

在第一個事件處理程序中(使用DOM0 級方法指定的),srcElement 屬性等於this,但在第二個事件處理程序中,這兩者的值不相同。

如前所述,returnValue 屬性相當於DOM 中的preventDefault()方法,它們的作用都是取消給定事件的默認行為。只要將returnValue 設置為false,就可以阻止默認行為。來看下面的例子。

var link = document.getElementById("myLink");
link.onclick = function(){
  window.event.returnValue = false;
};

這個例子在onclick 事件處理程序中使用returnValue 達到了阻止鏈接默認行為的目的。與DOM不同的是,在此沒有辦法確定事件是否能被取消。

相應地,cancelBubble 屬性與DOM 中的stopPropagation()方法作用相同,都是用來停止事件冒泡的。由於IE 不支持事件捕獲,因而只能取消事件冒泡;但stopPropagatioin()可以同時取消事件捕獲和冒泡。例如:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert("Clicked");
  window.event.cancelBubble = true;
};

document.body.onclick = function(){
  alert("Body clicked");
};

通過在onclick 事件處理程序中將cancelBubble 設置為true,就可阻止事件通過冒泡而觸發document.body 中註冊的事件處理程序。結果,在單擊按鈕之後,只會顯示一個警告框。

13.3.3 跨瀏覽器的事件對象

跨瀏覽器的寫法:

var EventUtil={
    addHandler:function(element,type,handler){
        if(element.addEventListener){
                element.addEventListener(type,handler,false);
        }else if(element.attachEvent){
            element.attachEvent("on"+type,handler);
        }else{
            element["on"+type]=handler;
        }
    },
    getEvent:function(event){
        return event?event:window.event;
    },
    getTarget:function(event){
        return event.target||event.srcElement;
    },
    preventDefault:function(event){
        if(event.preventDefault){
            event.preventDefault;
        }else{
            event.returnValue=false;
        }
    },
    removeHandler:function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler,false);
        }else if(element.datachEvent){
            element.detachEvent("on"+type,handler);
        }else{
            element["on"+type]=null;
        }
    },
    stopPropagation:function(event){
        if(event.stopPropagation){
            event.stopPropagation();
        }else{
            event.cancelBubble=true;
        }
    }
};

getEvent(),它返回對event對象的引用。考慮到IE 中事件對象的位置不同,可以使用這個方法來取得event 對象,而不必擔心指定事件處理程序的方式。在使用這個方法時,必須假設有一個事件對象傳入到事件處理程序中,而且要把該變量傳給這個方法。

btn.onclick = function(event){
    event = EventUtil.getEvent(event);
};

在兼容DOM 的瀏覽器中,event 變量只是簡單地傳入和返回。而在IE 中,event 參數是未定義的(undefined),因此就會返回window.event。將這一行代碼添加到事件處理程序的開頭,就可以確保隨時都能使用event 對象,而不必擔心用戶使用的是什麽瀏覽器。

getTarget(),它返回事件的目標。在這個方法內部,會檢測event 對象的target屬性,如果存在則返回該屬性的值;否則,返回srcElement 屬性的值。可以像下面這樣使用這個方法。

btn.onclick = function(event){
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
};

preventDefault(),用於取消事件的默認行為。在傳入event 對象後,這個方法會檢查是否存在preventDefault()方法,如果存在則調用該方法。如果preventDefault()方法不存在,則將returnValue 設置為false。下面是使用這個方法的例子。

var link = document.getElementById("myLink");
link.onclick = function(event){
  event = EventUtil.getEvent(event);
  EventUtil.preventDefault(event);
};

stopPropagation(),其實現方式類似。首先嘗試使用DOM 方法阻止事件流,否則就使用cancelBubble 屬性。下面看一個例子。

var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert("Clicked");
  event = EventUtil.getEvent(event);
  EventUtil.stopPropagation(event);
};
document.body.onclick = function(event){
  alert("Body clicked");
};

在此,首先使用EventUtil.getEvent()取得了event 對象,然後又將其傳入到EventUtil.stopPropagation()。別忘了由於IE 不支持事件捕獲,因此這個方法在跨瀏覽器的情況下,也只能用來阻止事件冒泡。

第13章 事件