《Javascript 高階程式設計(第三版)》筆記0x16 事件:事件流、事件處理程式
目錄
JavaScript 與 HTML 之間的互動是通過事件實現的。事件,就是文件或瀏覽器視窗中發生的一些特定的互動瞬間。可以使用偵聽器(或處理程式)來預訂事件,以便事件發生時執行相應的程式碼。這種在傳統軟體工程中被稱為觀察員模式的模型,支援頁面的行為(JavaScript 程式碼)與頁面的外觀(HTML 和 CSS 程式碼)之間的鬆散耦合。
事件流
事件流描述的是從頁面中接收事件的順序。
事件冒泡(event bubbling)
即事件開始時由最具體的元素(文件中巢狀層次最深的節點)接收,然後逐級向上傳播到較為不具體的節點(文件)。
<!DOCTYPE html> <html> <head> <title>Event Bubbling Example</title> </head> <body> <div id="myDiv">Click Me</div> </body> </html>
事件捕獲(event capturing)
事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於在事件到達預定目標之前捕獲它。
DOM事件流
“DOM2級事件”規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,為截獲事件提供了機會。然後是實際的目標接收到事件。最後一個階段是冒泡階段,可以在這個階段對事件做出響應。
事件處理程式
事件就是使用者或瀏覽器自身執行的某種動作。諸如 click、 load 和 mouseover,都是事件的名字。而響應某個事件的函式就叫做事件處理程式(或事件偵聽器)。事件處理程式的名字以"on"開頭,因此click事件的事件處理程式就是onclick,load 事件的事件處理程式就是 onload。
HTML事件處理程式
<input type="button" value="Click Me" onclick="alert("Clicked")" />
<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
這樣指定事件處理程式具有一些獨到之處。首先,這樣會建立一個封裝著元素屬性值的函式。這個函式中有一個區域性變數 event,也就是事件物件
<!-- 輸出 "click" -->
<input type="button" value="Click Me" onclick="alert(event.type)">
通過 event 變數,可以直接訪問事件物件,你不用自己定義它,也不用從函式的引數列表中讀取。
<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">
使用 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 元素一出現在頁面上就觸發相應的事件,但當時的事件處理程式有可能尚不具備執行條件。另一個缺點是,這樣擴充套件事件處理程式的作用域鏈在不同瀏覽器中會導致不同結果。不同 JavaScript引擎遵循的識別符號解析規則略有差異,很可能會在訪問非限定物件成員時出錯。
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
DOM0 級事件處理程式
每個元素(包括 window 和 document)都有自己的事件處理程式屬性,這些屬性通常全部小寫
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
};
使用 DOM0 級方法指定的事件處理程式被認為是元素的方法。因此,這時候的事件處理程式是在元素的作用域中執行;換句話說,程式中的 this 引用當前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); //"myBtn"
};
btn.onclick = null; //刪除事件處理程式
DOM2 級事件處理程式
“DOM2 級事件” 定義了兩個方法,用於處理指定和刪除事件處理程式的操作: addEventListener()和removeEventListener()。所有 DOM 節點中都包含這兩個方法,並且它們都接受 3 個引數:要處理的事件名、作為事件處理程式的函式和一個布林值。最後這個布林值引數如果是 true,表示在捕獲階段呼叫事件處理程式;如果是 false,表示在冒泡階段呼叫事件處理程式。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
使用 DOM2 級方法新增事件處理程式的主要好處是可以新增多個事件處理程式。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
通過 addEventListener()新增的事件處理程式只能使用 removeEventListener()來移除;移除時傳入的引數與新增處理程式時使用的引數相同。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
//這裡省略了其他程式碼
btn.removeEventListener("click", function(){ //沒有用!
alert(this.id);
}, false);
var btn = document.getElementById("myBtn");
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
//這裡省略了其他程式碼
btn.removeEventListener("click", handler, false); //有效!
IE事件處理程式
IE 實現了與 DOM 中類似的兩個方法: attachEvent()和 detachEvent()。這兩個方法接受相同的兩個引數:事件處理程式名稱與事件處理程式函式。由於 IE8 及更早版本只支援事件冒泡,所以通過attachEvent()新增的事件處理程式都會被新增到冒泡階段。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
在 IE 中使用 attachEvent()與使用 DOM0 級方法的主要區別在於事件處理程式的作用域。在使用 DOM0 級方法的情況下,事件處理程式會在其所屬元素的作用域內執行;在使用 attachEvent()方法的情況下,事件處理程式會在全域性作用域中執行,因此 this 等於 window。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert(this === window); //true
});
attachEvent()方法也可以用來為一個元素新增多個事件處理程式。不過,與 DOM方法不同的是,這些事件處理程式不是以新增它們的順序執行,而是以相反的順序被觸發。單擊這個例子中的按鈕,首先看到的是"Hello world!",然後才是"Clicked"。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
btn.attachEvent("onclick", function(){
alert("Hello world!");
});
使用 attachEvent()新增的事件可以通過 detachEvent()來移除,條件是必須提供相同的引數。與 DOM 方法一樣,這也意味著新增的匿名函式將不能被移除。不過,只要能夠將對相同函式的引用傳給 detachEvent(),就可以移除相應的事件處理程式。
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
btn.attachEvent("onclick", handler);
//這裡省略了其他程式碼
btn.detachEvent("onclick", handler);
跨瀏覽器的事件處理程式
第一個要建立的方法是 addHandler(),它的職責是視情況分別使用 DOM0 級方法、 DOM2 級方法或 IE 方法來新增事件。與 addHandler()對應的方法是 removeHandler(),它也接受相同的引數。這個方法的職責是移除之前新增的事件處理程式——無論該事件處理程式是採取什麼方式新增到元素中的,如果其他方法無效,預設採用 DOM0 級方法。
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;
}
}
};
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//這裡省略了其他程式碼
EventUtil.removeHandler(btn, "click", handler);