# 事件委派(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