DOM Level 2 事件模型



基本事件模型 的缺點之一,就是只能註冊一個事件處理器,如果你想註冊多個事件處理器,那麼類似以下的方式是行不通的:
window.onload = function() {
    // 處理器一
};
window.onload = function() {
    // 處理器二
};

在上例中,第二個函式實例會成為onload參考的對象,而第一個函式實例就不再有用了。如果你想在基本事件處理中,在事件發生時處理時呼叫兩個以上的函式,則必須透過設計的方式來達到,最簡單的方式之一就是...
function handler1() {
}
function handler2() {
}
window.onload = function() {
    handler1();
    handler2();
};

但通常這類設計方式不好管理。事件模型在DOM Level 2時獲得標準化,所以又稱為標準事件模型DOM Level 2事件模型允 許你一次註冊兩個以上的事件,不過DOM Level 2並沒有獲得Internet Explorer的支援,所以接下來的程式,僅可在支援DOM Level 2標準事件的瀏覽器上執行。(在Internet Explorer 9之後,DOM Level 2事件模型終於得到比較好的支援。)

在DOM Level 2事件模型中,要註冊事件,必須使用addEventListener()方法。舉個例子來說,若要以DOM Level 2事件模型實現 基本事件模型 中第一個範例,可以如下:
<html>
<head>
<title>DOML2EventModel-1</title>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<script type="text/javascript">
window.addEventListener('load', function() {
function handler() {
document.getElementById('console').innerHTML
= 'Who\'s clicked: ' + this.id;
}
document.getElementById('btn1')
.addEventListener('click', handler, false);
document.getElementById('btn2')
.addEventListener('click', handler, false);
}, false);
</script>
</head>
<body>
<button id="btn1">按鈕一</button><br>
<button id="btn2">按鈕二</button><br>
<div id="console"></div>
</body>
</html>

addEventListener()的第一個參數指出要註冊的事件,不需要'on'開頭,第二個參數是事件處理器,第三個參數為false時,表示這是個事件浮昇處理器(之後再說明)。

在上例中,於事件處理器中使用this取得目前觸發事件的元素,雖然目前多數遵守
DOM Level 2的瀏覽器都會如此實作,不過這並不是DOM Level 2標準的規範,在DOM Level 2的標準中,可以從Event實例的currentTarget特性來取得目前觸發事件的元素。

DOM Level 2事件模型下,Event會作為事件處理器的第一個參數傳入,有些事件有預設的動作,若要停止預設動作,在DOM Level 2事件模型下,必須呼叫Event的preventDefault()方法(而不是從事件處理器中傳回false)。例如可改寫 基本事件模型 中第三個表單驗證範例
<html>
<head>
<title>DOML2EventModel-2</title>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<script type="text/javascript">
window.addEventListener('load', function() {
document.form1.addEventListener('submit', function(event) {
if(event.currentTarget.data.value.length === 0) {
event.preventDefault();
}
}, false);
}, false);
</script>
</head>
<body>
<form name="form1" action="fake.do">
輸入資料:<input name="data"><br>
<button type="submit">送出</button>
</form>
</body>
</html>

在DOM Level 2事件模型中,事件會歷經兩個傳播階段,當事件發生時,會先從document往內傳播至操作目標元素,這個階段稱之為捕捉階段(Capturing phase),接著事件再從操作目標元素往外傳播至document,這個階段稱之為氣泡階段(Bubbling phase)addEventListener()方法的第三個參數若為true,表示事件處理器將作為捕捉階段處理器,若為false則為氣泡階段處理器。

例如,可改寫 Event 實例 中第一個範例,同時設定兩個階段的處理器來觀察事件:
<html>
<head>
<title>DOML2EventModel-3</title>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<script type="text/javascript">
window.addEventListener('load', function() {
function handler(event) {
document.getElementById('console').innerHTML +=
'<br><b>currentTarget.id:</b> '
+ event.currentTarget.id +
', <b>target.id:</b> ' + event.target.id;
}
document.getElementById('bodyId')
.addEventListener('click', handler, true);
document.getElementById('bodyId')
.addEventListener('click', handler, false);
document.getElementById('divId')
.addEventListener('click', handler, true);
document.getElementById('divId')
.addEventListener('click', handler, false);
document.getElementById('btnId')
.addEventListener('click', handler, true);
document.getElementById('btnId')
.addEventListener('click', handler, false);
}, false);
</script>
</head>
<body id="bodyId">
<div id="divId"><button id="btnId">按我</button></div>
<span id="console"></span>
</body>
</html>

操作的目標元素,可以使用Event的target特性取得。如果按下按鈕,會發現以下的結果,可發現事件先從外往內,再從內往外傳播:
currentTarget.id: bodyId, target.id: btnId
currentTarget.id: divId, target.id: btnId
currentTarget.id: btnId, target.id: btnId
currentTarget.id: btnId, target.id: btnId
currentTarget.id: divId, target.id: btnId
currentTarget.id: bodyId, target.id: btnId

如果你想要停止事件傳播,必須呼叫Event的stopPropagation()方法。例如上一個範例若僅註冊浮昇處理器,要停止目標元素外的事件傳播,可以如下:
<html>
<head>
<title>DOML2EventModel-4</title>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<script type="text/javascript">
window.addEventListener('load', function() {
function handler(event) {
document.getElementById('console').innerHTML +=
'<br><b>currentTarget.id:</b> '
+ event.currentTarget.id +
', <b>target.id:</b> ' + event.target.id;
event.stopPropagation();
}

document.getElementById('bodyId')
.addEventListener('click', handler, false);
document.getElementById('divId')
.addEventListener('click', handler, false);
document.getElementById('btnId')
.addEventListener('click', handler, false);
}, false);
</script>
</head>
<body id="bodyId">
<div id="divId"><button id="btnId">按我</button></div>
<span id="console"></span>
</body>
</html>

若要移除事件處理器,則可以使用removeEventListener(),第一個參數指定事件類型,第二個參數是當初註冊的函式實例,第三個參數指出要移除捕捉階段(true)或浮昇階段(false)的處理器。