JS 基礎篇 (10) Event Listeners

事件偵聽

如果要在網頁上與使用者互動時,必須在對應的元素上綁定事件偵聽 addEventListener 函式。

1
2
3
4
const parent = document.querySelector('.parent');
parent.addEventListener('click', e => {
console.log(e);
});

addEventListener() 基本上有三個參數,分別是「事件名稱」、「事件的處理器」(事件觸發時執行的 function),以及一個「Boolean」值,由這個 Boolean 決定事件是以「捕獲」或「冒泡」機制執行,若不指定則預設為「冒泡」。

事件捕獲與冒泡

JS 觸發事件的流程可以分為兩個機制:

  • 事件冒泡 (Event Bubbling)
  • 事件捕獲 (Event Capturing)

事件冒泡 (Event Bubbling)

事件冒泡指的是「從啟動事件的元素節點開始,逐層往上傳遞」,直到整個網頁的根節點,也就是 document。

事件冒泡

事件捕獲 (Event Capturing)

事件捕獲是由上往下傳遞,從document 到 html 到body 最後元素本身。

事件捕獲

既然事件傳遞順序有兩種機制,那我怎麼知道事件是依賴哪種機制執行的?

答案是兩種都會觸發,如下圖:

當 td 的 click 事件發生時,會先走紅色的 「capture phase」,上而下依序觸發它們的 click 事件。然後再繼續執行綠色的 「bubble phase」,反方向由 一路往上傳至 Document,整個事件流程到此結束。

阻止事件傳遞

現在我們知道事件的傳遞流程會先經由捕獲機制再到冒泡機制,若想阻止事件的傳遞,可用 stopPropagation。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const grantParent = document.querySelector('.grantParent');
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');

grantParent.addEventListener('click', e => {
console.log('grantParent');
});

parent.addEventListener('click', e => {
e.stopPropagation();
console.log('parent');
});

child.addEventListener('click', e => {
console.log('child');
});

如上我們在 parent 的事件綁定上增加 e.stopPropagation() ,來阻止事件往上層 grantParent 傳遞。

只觸發一次事件

若想限制事件只觸發一次,可以將 once 參數設為 true

1
2
3
4
5
6
const child = document.querySelector('.child');

child.addEventListener('click', e => {
console.log('child');
}, { once: true });

解除事件偵聽

若要取消事件偵聽,可以用 removeEventListener(),要注意的是取消偵聽的函式必須是同一個,所以函式要單獨宣告。

1
2
3
4
5
6
7
8
9
10
11
12
const child = document.querySelector('.child');

function sayHi() {
console.log('sayHi');
}

child.addEventListener('click', sayHi);

setTimeout(() => {
child.removeEventListener('click', sayHi);
}, 2000);

事件指派

當網頁上的元素會動態新增或刪除時,若想對動態增減的元素做事件偵聽,可以用事件指派的方式。

  • 事件指派(event delegation): 在外層元素做事件綁定,利用事件捕獲往下傳遞的機制,來選擇觸發事件的目標。
1
2
3
4
5
6
7
8

const child = document.querySelector('.container');

container.addEventListener('click', e => {
if (e.target.matches('div')) {
console.log('div')
}
});

如上,在外層 container 做事件偵聽,當點擊到的是 div 時才處發函式。

Handler 中的 event

當監聽事件發生,透過 addEventListner 會去觸發註冊的 event handler ,也就是指定的函式。
此時 event handler 會去建立一個「事件物件」,裡面包含了與這個事件相關的屬性,並以參數的形式傳給handler。

常用的屬性:

  1. e.target:触发事件的元素。
  2. e.currentTarget:当前正在处理事件的元素(即事件处理函数所附加的元素)。
  3. e.type:事件的类型(例如,”click”、”mouseover” 等)。
  4. e.preventDefault():阻止事件的默认行为。
  5. e.stopPropagation():停止事件在 DOM 层次结构中的传播。
  • e.target 與 e.currentTarget 差異

1
2
3
4
5
6
7
8
9
10
11
<button id="myButton">
<span>Click Me</span>
<svg width="20">
</svg>
</button>
<script>
document.getElementById('myButton').addEventListener('click', function(e) {
console.log('Clicked element:', e.target); // 可能是 span、svg、button
console.log('Clicked element:', e.currenTarget); // myButton
});
</script>

當今天有一顆按鈕,裡面包有 span、svg 不同元素時

  • e.target: 會指向我此時點擊的元素,點到 span 就指向span,點到 svg 就指向 svg
  • e.currentTarget: 永遠指向事件綁定的元素。