在 Day 11 中,我們學習了基本的事件監聽和處理方式,這些適合處理單一或少量的 DOM 元素。但當我們需要處理大量元素或動態新增的內容時,這些基本方法可能會導致效能問題。今天,我們將進一步探索更高效的事件處理方式,特別是事件代理(Event Delegation),這是一種適合管理大量或動態生成元素的技術。
1.事件冒泡與捕獲
在理解事件代理之前,首先要熟悉事件冒泡(Event Bubbling)和事件捕獲(Event Capturing)。這是瀏覽器處理事件的兩個過程。
事件冒泡: 當事件在某個子元素上觸發時,會逐層往上傳播到父元素,直到根節點。
事件捕獲: 與冒泡相反,事件會從最外層的父元素開始向內層子元素傳遞,直到目標元素。
大部分事件默認是以「冒泡」的方式傳播。
// 範例:點擊子元素,事件冒泡到父元素
document.getElementById("parent").addEventListener("click", () => {
console.log("父元素被點擊");
});
document.getElementById("child").addEventListener("click", () => {
console.log("子元素被點擊");
});
// 點擊子元素時,會先輸出 "子元素被點擊",再輸出 "父元素被點擊"
2.事件代理的概念
當我們需要為多個相同類型的元素添加事件處理器時,直接為每個元素添加事件監聽器是低效的,特別是在有很多元素或這些元素是動態生成的情況下。這時,事件代理 提供了一個更高效的解決方案。
事件代理的核心原理 是利用事件冒泡的特性,將事件監聽器綁定到元素的父層,通過檢查事件目標(event.target),來確定具體是哪個子元素觸發了事件。
範例:使用事件代理處理大量按鈕點擊
假設有一組按鈕,我們希望為每個按鈕添加點擊事件處理器,透過事件代理可以只在父層綁定一個事件處理器。
<div id="button-container">
<button class="btn">按鈕1</button>
<button class="btn">按鈕2</button>
<button class="btn">按鈕3</button>
</div>
傳統做法是為每個按鈕添加單獨的點擊事件處理器:
const buttons = document.querySelectorAll(".btn");
buttons.forEach(button => {
button.addEventListener("click", () => {
console.log(button.textContent + " 被點擊");
});
});
使用事件代理,我們可以只在父層 #button-container 上添加一個監聽器:
document.getElementById("button-container").addEventListener("click", (event) => {
if (event.target.classList.contains("btn")) {
console.log(event.target.textContent + " 被點擊");
}
});
3.事件代理的優勢
效能提升: 當處理大量元素時,事件代理只需要在父層添加一個監聽器,避免為每個子元素單獨添加處理器,節省了內存和運算資源。
動態內容支持: 事件代理能夠自動處理動態添加的子元素,因為監聽器綁定在父層,不需要手動為新增的元素重新添加事件處理器。
簡化程式碼: 事件代理可以簡化事件處理的程式碼,減少重複的監聽器綁定。
4.捕獲階段中的事件代理
如果我們想要在事件的捕獲階段進行處理,可以將 addEventListener 的第三個參數設為 true,這樣事件就會在捕獲階段處理,而不是冒泡階段。
document.getElementById("button-container").addEventListener("click", (event) => {
if (event.target.classList.contains("btn")) {
console.log(event.target.textContent + " 被捕獲");
}
}, true); // true 表示在捕獲階段處理
這樣做會改變事件的處理順序,讓父層先捕捉到事件,然後才會傳到子元素。