iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
Modern Web

30天一起搞懂Web觀念系列 第 6

[DAY6] addEventListener 事件捕獲與冒泡是什麼?

  • 分享至 

  • xImage
  •  

addEventListener 的事件捕獲與冒泡是什麼?

在寫網頁的時候,我們常常會需要監聽使用者的操作來觸發一些行為,而使用者的某個操作(如點擊按鈕、按下鍵盤…)就叫做事件(Event)

最常見的例子就是按下一個按鈕的點擊事件

一開始學的時候,都會直接寫在HTML裡,這樣很不好維護

<button onclick="alert('Hello!')">Click me</button>

但這樣很不好維護,所以當我們學了JS,就把它寫在JS

<button id="btn">Click me</button>
const btn = document.getElementById("btn");
btn.onclick = function () {
  alert("Hello!");
};

但這種方式有個問題:它只能指定一個事件處理函式,後面會蓋掉前面,像下面這樣

btn.onclick = function () {
  alert("First");
};

btn.onclick = function () {
  alert("Second"); // 第一個被蓋掉了
};

所以我們就學了addEventListener

btn.addEventListener("click", () => {
  alert("First");
});

btn.addEventListener("click", () => {
  alert("Second");
});

這兩個都會執行!

這是因為他綁定的方式支援:

  • 同一個元素綁定多個事件處理器
  • 支援事件的冒泡(bubble)捕獲(capture)

什麼是事件冒泡(Bubble)與事件捕獲(Capture)?

當你點擊一個 HTML 元素時,事件不會只發生在那個元素身上,它會從你點的元素往上傳遞(Bubble),也可以設定成從最外層往內傳遞(Capture)

這就叫做事件傳遞機制,它包含三個階段:

  1. 捕獲階段(Capture phase):從最外層的 document 往內傳遞
  2. 目標階段(Target phase):事件抵達你點擊的那個元素
  3. 冒泡階段(Bubble phase):從內層目標元素往上冒泡到外層 document

看一個例子:

<div id="outer">
  <button id="inner">Click me</button>
</div>

document.getElementById("outer").addEventListener(
  "click",
  () => {
    console.log("Outer clicked");
  },
  false // false 表示在冒泡階段處理
);

document.getElementById("inner").addEventListener(
  "click",
  () => {
    console.log("Inner clicked");
  },
  false
);

點下 button,會看到:

Inner clicked
Outer clicked

這表示事件先從 inner 開始觸發,然後再冒泡到 outer


那如果改成true ,就會變成Capture


document.getElementById("outer").addEventListener(
  "click",
  () => {
    console.log("Outer clicked");
  },
  true // true 表示在捕獲階段處理
);

document.getElementById("inner").addEventListener(
  "click",
  () => {
    console.log("Inner clicked");
  },
  true
);

console就會長這樣,先Outer再Inner

Outer clicked
Inner clicked

比較一下

傳遞階段 執行順序 參數
捕獲 外 → 內 第三個參數設 true
冒泡 內 → 外 第三個參數設 false(預設)

為什麼需要事件捕獲 / 冒泡?

這種事件傳遞機制,讓我們可以做事件代理(Event Delegation),透過把事件只綁在外層,來管理內部多個元素的事件

例如:

<ul id="list">
  <li class="item">1</li>
  <li class="item">2</li>
</ul>
document.getElementById("list").addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    alert(`Clicked ${e.target.textContent}`);
  }
});

// 注意:
// 這樣寫是錯的,因為click只會在點擊的時候執行,但是迴圈會先跑完
// const items = document.querySelectorAll('.item')
// for (i = 0; i < btns.length; i += 1) {
//     items[i].addEventListener('click', () => {
//       alert(i)
//    })
// }

這樣新增很多 <li>,都不用一個個綁定!


Note:

e 是事件物件(event object),是瀏覽器在事件發生時自動傳進來的參數,裡面很多關於這次事件的細節,像是這次的事件類型(type:"click" )、觸發事件的元素(target:li )、滑鼠相對於視窗的座標(clientX: 51clientY: 82)…


Note:阻止冒泡或捕獲

e.stopPropagation();

這個方法可以停止事件繼續冒泡或捕獲,就是不要再讓事件傳下去了


參考資料

https://ithelp.ithome.com.tw/articles/10191970

https://hackmd.io/@Heidi-Liu/note-fe201-dom

https://www.explainthis.io/zh-hant/swe/fe-event-delegation-capture-bubble


上一篇
[DAY5] DOM 是什麼?
下一篇
[DAY7] JS 的同步與非同步是什麼?(1)
系列文
30天一起搞懂Web觀念30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言