事件冒泡、事件捕獲

介紹事件冒泡和事件捕獲,並解釋如何使用 addEventListener 來控制事件流。


什麼是事件冒泡?

事件冒泡是指當一個元素觸發事件時,事件會從這個元素逐層往外傳遞。例如,當你點擊按鈕時,事件會先在按鈕上觸發,然後傳到外層的 div,再傳到更外層的元素(例如 body)。這樣我們可以在較高層的元素上處理事件,而不用為每個小元素都加監聽器,從而減少重複代碼、提升性能和維護性。

1
2
3
<div id="container">
<button id="button">Click Me</button>
</div>
1
2
3
4
5
6
7
document.getElementById('container').addEventListener('click', () => {
console.log('Container clicked');
});

document.getElementById('button').addEventListener('click', (e) => {
console.log('Button clicked');
});

當你點擊按鈕時,控制台會先顯示 “Button clicked”,然後顯示 “Container clicked”,這就是事件冒泡的效果。

什麼是事件捕獲?

事件捕獲與冒泡相反,事件從最外層元素開始逐層往內傳遞,直到目標元素。例如,當你點擊 inner 這個 div 時,如果使用事件捕獲,事件會先從最外層的 outer 開始,再傳遞到 inner。事件捕獲可以用來在事件到達目標元素之前進行特殊處理。

事件流的圖解

下面這個圖表展示了事件捕獲和事件冒泡的過程:

事件流圖解

  • 捕獲階段(Capture Phase):事件從 document 開始,逐層往下傳遞,直到目標元素。
  • 目標階段(Target Phase):事件到達目標元素。
  • 冒泡階段(Bubbling Phase):事件從目標元素逐層往上傳遞,直到 document

使用 addEventListener

在 JavaScript 中,我們可以使用 addEventListener 為元素添加事件監聽器。addEventListener 方法有三個主要參數以及一個可選的物件參數:

  1. 事件類型(例如 click):指定要監聽的事件。
  2. 事件處理函數:當事件發生時執行的函數。
  3. 可選參數(捕獲或冒泡):布爾值,決定事件是在捕獲階段還是冒泡階段處理。true 表示捕獲,false 或不傳表示冒泡。
  4. 可選參數(options:這是一個物件,可以包含以下屬性:
    • capture:與原本用來表示「捕獲」或「冒泡」的機制相同。
    • once:代表這個事件只會被觸發一次,結束後就自動解除事件監聽。
    • passive:當設定成 true 時,表示這個事件處理器不會呼叫 event.preventDefault() 這個方法。如果開發者呼叫了 event.preventDefault() 時,瀏覽器會直接忽略,並在 console 主控台顯示警告訊息。

event.preventDefault()用於阻止元素的預設行為。
例如,點擊一個連結時,它通常會跳轉到新的頁面;使用event.preventDefault()可以阻止這種行為。
同樣地,在提交表單時也可以用來防止頁面重新加載。如果開發者呼叫了event.preventDefault()時,瀏覽器會直接忽略,並在 console 主控台顯示警告訊息。

例如:

1
2
3
4
5
document.getElementById('button').addEventListener('click', myClickHandler, {
once: true,
passive: true,
capture: true
});

在這個例子中,button 的點擊事件只會觸發一次,並且在觸發後會自動移除,同時設置為被動模式以提升性能。

事件冒泡與捕獲的範例

1
2
3
<div id="outer">
<div id="inner">Inner Div</div>
</div>
1
2
3
4
5
6
7
document.getElementById('outer').addEventListener('click', () => {
console.log('Outer Div clicked');
}, true); // 捕獲階段

document.getElementById('inner').addEventListener('click', () => {
console.log('Inner Div clicked');
});//冒泡階段 (不填參數、false)

當你點擊 inner 這個 div 時,控制台會先顯示 “Outer Div clicked”,再顯示 “Inner Div clicked”,因為 outer 使用了捕獲階段。

停止事件傳遞

有時候,我們希望事件不要繼續傳遞,可以使用 stopPropagation() 方法。

1
2
3
4
document.getElementById('inner').addEventListener('click', (e) => {
e.stopPropagation();
console.log('Inner Div clicked');
});

當你點擊 inner 這個 div 時,事件不會再往外傳,因此 “Outer Div clicked” 不會顯示。

總結

  • 事件冒泡:事件從目標元素逐層往外傳遞。
  • 事件捕獲:事件從外層元素逐層往內傳遞。
  • addEventListener:可以用第三個參數決定事件處理的階段(捕獲或冒泡)。
  • stopPropagation():用來阻止事件的傳遞。