在事件發生時,會有個 Event
實例收集事件的相關資訊,在遵守標準的瀏覽器上,Event
實例會作為事件處理器的的第一個參數,若要取得操作的目標物件,可以透過 Event
實例的 target
特性。
那麼操作的目標物件是指什麼呢?如果在按鈕上點選,那麼按鈕就是操作的目標物件,在〈基本事件模型〉中有說明過,觸發事件時,事件處理器的 this
會設定為當時的元素,那麼為何還要有特性指出操作目標物件?
事實上,在〈基本事件模型〉中,操作時若發生事件,並事件不僅停於操作的元素,還會從操作的元素往外傳播,若外層元素亦有設定對應的事件處理器,亦會呼叫事件處理器,這可以用下面的範例來示範:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">
<div id="divId">
<button id="btnId">按我</button>
</div>
<span id="console"></span>
<script type="text/javascript">
function handler(event) {
let target = event.target;
document.getElementById('console').innerHTML +=
`<br><b>this.id:</b> ${this.id}, <b>target.id:</b> ${target.id}`;
}
document.getElementById('bodyId').onclick = handler;
document.getElementById('divId').onclick = handler;
document.getElementById('btnId').onclick = handler;
</script>
</body>
</html>
在上例中,按鈕是包括在 <div>
中,而 <body>
是 <div>
的外層元素,三者都設定了事件處理器。如果試著按下按鈕,則會看到結果如下:
this.id: btnId, target.id: btnId
this.id: divId, target.id: btnId
this.id: bodyId, target.id: btnId
不僅按鈕的事件處理器被呼叫,外層 <div>
與 <body>
也依序被呼叫,這樣的行為叫作事件氣泡傳播(Event Bubbling),事件傳播至元素並呼叫事件處理器時,this
就設定為該元素,這可以從 this.id
的顯示結果觀察到,並注意到,由於操作時按下的是按鈕,所以操作目標元素就是按鈕,這可以由 target.id
觀察到。
事件氣泡傳播可以善用。例如在〈修改文件〉中動態新增圖片的例子,每建立一個新的 <img>
,就設定該 <img>
的 click
事件處理器,以便在點選圖片時自動移除圖片。若利用事件氣泡傳播,可以只在 <div>
上設定一次事件處理器,完成相同的結果。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<input id="src" type="text"><button id="add">新增圖片</button>
<div id="images"></div>
<script type="text/javascript">
let images = document.getElementById('images');
images.onclick = function(event) {
this.removeChild(event.target);
};
document.getElementById('add').onclick = function() {
let img = document.createElement('img');
img.src = document.getElementById('src').value;
images.appendChild(img);
};
</script>
</body>
</html>
如果想要停止事件傳播,在遵守標準的瀏覽器上,必須呼叫 Event
的 stopPropagation()
方法。例如要將第一個範例停止目標元素外的事件傳播,可以如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">
<div id="divId">
<button id="btnId">按我</button>
</div>
<span id="console"></span>
<script type="text/javascript">
function handler(event) {
let target = event.target;
document.getElementById('console').innerHTML +=
`<br><b>this.id:</b> ${this.id}, <b>target.id:</b> ${target.id}`;
event.stopPropagation();
}
document.getElementById('bodyId').onclick = handler;
document.getElementById('divId').onclick = handler;
document.getElementById('btnId').onclick = handler;
</script>
</body>
</html>
在〈DOM Level 2 事件模型〉中,事件會歷經兩個傳播階段,當事件發生時,會先從 document
往內傳播至操作目標元素,這個階段稱之為捕捉階段(Capturing phase),接著事件再從操作目標元素往外傳播至 document
,這個階段稱之為氣泡階段(Bubbling phase)。
addEventListener
方法的第三個參數若為 true
,表示事件處理器將作為捕捉階段處理器,若為 false
則為氣泡階段處理器。
例如,可改寫第一個範例,同時設定兩個階段的處理器來觀察事件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">
<div id="divId">
<button id="btnId">按我</button>
</div>
<span id="console"></span>
<script type="text/javascript">
function handler(event) {
let currentTarget = event.currentTarget;
let target = event.target;
document.getElementById('console').innerHTML +=
`<br><b>currentTarget.id:</b> ${currentTarget.id}, <b>target.id:</b> ${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);
</script>
</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
方法。例如上一個範例若僅註冊浮昇處理器,要停止目標元素外的事件傳播,可以如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body id="bodyId">
<div id="divId">
<button id="btnId">按我</button>
</div>
<span id="console"></span>
<script type="text/javascript">
function handler(event) {
let currentTarget = event.currentTarget;
let target = event.target;
document.getElementById('console').innerHTML +=
`<br><b>currentTarget.id:</b> ${currentTarget.id}, <b>target.id:</b> ${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);
</script>
</body>
</html>