---
# System prepended metadata

title: 事件委派（Event Delegation）

---

# 事件委派（Event Delegation）

**事件委派（Event Delegation）或稱事件委託。**
是一種將事件監聽器直接添加到父元素(而非子元素)的技術。
每當父元素底下的子元素被觸發時，事件監聽器就會因為DOM的事件傳遞機制-事件冒泡而被觸發。

**事件委派的特色：**
減少記憶體空間：因為只需綁定一個處理程序在父元素上，我們不必在為每個子元素上綁定事件，或從已刪除的元素或新增的元素中綁定事件監聽處理器。

## 先複習一下何為DOM的事件傳遞機制？(~~趕時間的人可以直接跳過這段~~~)

**事件傳遞機制(Event Propagation)**，或說**事件流程/事件流(Event Flow)**

先假設網頁有一個按鈕 html結構如下：
```
<body>
    <div id="outer">
        <button id="btn">click</button>
    </div>
</body>
```
![](https://i.imgur.com/tR8B8cJ.png =250x200)


我們點擊button的時候，其實也點到了body和div標籤，在看遠一點也可以說實際上我們點到整個網頁。
事件傳遞機制會探討的是：事件觸發時網頁接收事件的順序。

事件傳遞機制可分為兩種模式：

* **事件捕獲(Event Capturing)**
從被觸發事件的元素結點開始，逐漸往下層傳遞。
上面的例子來看，點擊button後，事件捕獲的機制下觸發順序會是：
    1. body 標籤
    2. div 標籤
    3. button 標籤


* **事件冒泡(Event Bubbling)**
從被觸發事件的元素結點開始，~~啵啵啵~~逐漸往上層傳遞。
事件冒泡的機制下觸發順序會是：
    1. button 標籤
    2. div 標籤
    3. body 標籤


要檢驗事件傳遞機制，會用addListener()綁定事件。
我們經常使用addListener()前兩個函數，實際上還有第三個函數：布林值。
這個布林值決定了事件是以「捕獲(true)」或「冒泡機制(false)」執行。

> addListener(事件名稱, 事件處理器function, 布林值)

若不指定，**預設會是false，也就是冒泡機制**。

我們用剛剛的按鈕結構，實際用addListener()綁定各三個物件，使用捕獲機制和冒泡機制一起執行以下程式：

```＋
// 冒泡機制
document.getElementById("btn").addEventListener('click',()=>{
  console.log("Bubble, button clicked")
},false)
document.getElementById("outer").addEventListener('click',()=>{
  console.log("Bubble, div clicked")
},false)
document.body.addEventListener('click',()=>{
  console.log("Bubble, body clicked")
},false)

// 捕獲機制
document.getElementById("btn").addEventListener('click',()=>{
  console.log("Capture, button clicked")
}, true)
document.getElementById("outer").addEventListener('click',()=>{
  console.log("Capture, div clicked")
}, true)
document.body.addEventListener('click',()=>{
  console.log("Capture, body clicked")
}, true)
```
執行程式得到以下順序：
"Capture, body clicked"
"Capture, div clicked"
"Capture, button clicked"
"Bubble, button clicked"
"Bubble, div clicked"
"Bubble, body clicked"

先捕獲後冒泡。


### 如何阻止事件傳遞機制？
當畫面處理變得很複雜，可以使用**停止事件(stopPropagation)**，阻止當前事件繼續進行捕捉或冒泡的傳遞。
以上面的程式碼中作測試，我們在捕獲機制的outer ID的點擊事件裡，新增停止事件：
```+
document.getElementById("outer").addEventListener('click',(e)=>{
  e.stopPropagation() 
  console.log("Capture, div clicked")
}, true)
```
然後執行程式，結果會得到：
"Capture, body clicked"
"Capture, div clicked"
程式執行到捕獲機制的div標籤時就停止傳遞了。



## 回到重點，事件委派的舉例：
假設我們有三個按鈕，事先配給他們屬性，html結構如下:
```+
<div id="menu">
  <button data-key="add">新增</button>
  <button data-key="update">修改</button>
  <button data-key="delete">移除</button>
</div>
```
![](https://i.imgur.com/rztMRCP.png =300x90)


我們可以僅為父元素的menu ID綁定事件偵聽器如下：
```+
document.getElementById("menu")
menu.addEventListener("click",()=>{
  console.log("clicked")
})
```
於是我們無論點哪個按鈕，都可以被偵測到點擊事件：
"clicked"
"clicked"
"clicked"


接著我們會想知道到底使用者點到的是哪個按鈕，作出區隔。
我們可以利用事件物件來取得目標物件的屬性，識別出點擊的當下是哪個屬性，對應到該物件。
```+
document.getElementById("menu")
menu.addEventListener("click",(e)=>{
  let key = e.target.getAttribute("data-key")
  console.log(key)
})
```
接著點新增 修改 移除 按鈕就能對應到各自的屬性名稱：
"add"
"update"
"delete"

**所以優點!!我們不必為每個按鈕(子元素)都綁定事件監聽器。
也可以知道，如果要在menu ID這個父元素底下新增或移除按鈕時，也都不用再另外綁定事件或移除事件了。**

#### 這就是事件委派，我們僅將事件監聽器綁在父元素上，每當父元素底下的子元素被點擊時，父元素的事件監聽器就會因為事件冒泡而被觸發。




---

參考資料：
彭彭直播 - 事件傳遞機制和事件委託 Event Propagation & Event Delegation
https://www.youtube.com/watch?v=3cTdztTmtL0&t=6621s
重新認識 JavaScript: Day 14 事件機制的原理
https://ithelp.ithome.com.tw/articles/10191970?sc=iThelpR
Explain event delegation
https://github.com/yangshun/front-end-interview-handbook/blob/master/contents/en/javascript-questions.md#explain-event-delegation