關於JavaScript事件冒泡和事件捕獲

簡單記錄了關於event中冒泡和捕獲


在網頁開發時,我們不可避免的會遇到各種互動事件,所以理解事件處理中常見的事件冒泡(Event Bubbling)和事件捕獲(Event Capturing)的機制,不僅能提高開發效率,還能打造更流暢的用戶體驗。

這裡將深入解析這兩個概念,並通過實例說明它們的應用。

事件傳遞過程

在討論事件冒泡和事件捕獲之前,先簡要說明事件的傳遞過程。事件傳遞分為三個階段:

  1. 捕獲階段(Capture phase):事件從 window 向下傳播至目標元素。
  2. 目標階段(Target phase):事件抵達目標元素。
  3. 冒泡階段(Bubbling phase):事件從目標元素向上冒泡至頂層元素。

以下是一個例子來說明事件的傳遞過程:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <form>
      <div>
        <p>target</p>
      </div>
    </form>
 
    <script>
      for (let element of document.querySelectorAll('*')) {
        element.addEventListener(
          'click',
          (event) => console.log(`Capture: ${element.tagName}`),
          true
        );
        element.addEventListener('click', (event) =>
          console.log(`Bubble: ${element.tagName}`)
        );
      }
    </script>
  </body>
</html>

當我們點擊 <p> 元素時,會先從上往下捕獲,再從下往上冒泡。

我們能得到一個結論:

當event發生時,發生該事件的元素被標記成目標元素(event.target)。然後事件從根結點向下移動到event.target。

並在經過的途中調用分配了addEventListener(..., true)的處理方法。

然後,在目標元素上調用該處理方法。

最後,事件從event.target開始冒泡回跟節點,並調用on<event>、HTML attribute以及addEventListener

什麼是事件冒泡?

事件冒泡指的是事件從最深的元素開始觸發,然後逐層向上傳播至頂層的過程。這意味著,如果一個事件發生在某個元素上,它會首先觸發該元素的事件處理器,然後逐層向上觸發其父級元素的事件處理器,直到達到 documentwindow 對象。

事件冒泡的例子

假設我們有以下 HTML 結構:

<body>
  <div id="parent">
    <button id="child">點擊我</button>
  </div>
</body>

我們可以為 divbutton 元素分別添加點擊事件處理器:

document.getElementById('child').addEventListener('click', function () {
  alert('按鈕被點擊');
});
 
document.getElementById('parent').addEventListener('click', function () {
  alert('父元素被點擊');
});

當點擊按鈕時,會先彈出「按鈕被點擊」,然後彈出「父元素被點擊」,這就是事件冒泡的過程。

什麼是事件捕獲?

事件捕獲與事件冒泡相反,指的是事件從頂層元素開始,逐層向下傳播至目標元素的過程。事件捕獲較少使用,但在某些特殊情況下非常有用。

事件捕獲的例子

我們可以修改上一個例子來演示事件捕獲:

document.getElementById('child').addEventListener(
  'click',
  function () {
    alert('按鈕被點擊');
  },
  true
);
 
document.getElementById('parent').addEventListener(
  'click',
  function () {
    alert('父元素被點擊');
  },
  true
);

這裡,我們將第三個參數設為 true,表示在事件捕獲階段觸發事件處理器。這樣,當點擊按鈕時,會先彈出「父元素被點擊」,然後彈出「按鈕被點擊」。

事件冒泡與事件捕獲的區別

  • 事件冒泡:從目標元素開始,逐層向上傳播至頂層元素。
  • 事件捕獲:從頂層元素開始,逐層向下傳播至目標元素。

如何控制事件傳播

使用 stopPropagation

有時候,我們需要阻止事件傳播。這時可以使用 stopPropagation 方法:

document.getElementById('child').addEventListener('click', function (event) {
  event.stopPropagation();
  alert('按鈕被點擊');
});
 
document.getElementById('parent').addEventListener('click', function () {
  alert('父元素被點擊');
});

這樣,點擊按鈕時,只會彈出「按鈕被點擊」,不會彈出「父元素被點擊」。

使用 preventDefault

preventDefault 用於阻止事件的默認行為,例如鏈接跳轉或表單提交:

document.getElementById('myLink').addEventListener('click', function (event) {
  event.preventDefault();
  alert('鏈接點擊被攔截');
});

Reference:

UI Events