在網頁開發中,我們經常需要為 DOM 元素添加事件監聽器。但是當頁面中有大量元素需要相同的事件處理時,為每個元素單獨添加監聽器會造成性能問題。這時候就需要用到事件代理(Event Delegation)。
什麼是事件代理
事件代理是一種事件處理技術,它利用了事件冒泡的原理,將事件監聽器添加到父元素上,而不是直接添加到目標元素上。當子元素觸發事件時,事件會冒泡到父元素,父元素的監聽器就可以處理這個事件。
事件冒泡機制
在了解事件代理前,我們先來了解事件冒泡:
<div id="parent">
<button id="child">點擊我</button>
</div>
document.getElementById('parent').addEventListener('click', () => {
console.log('父元素被點擊');
});
document.getElementById('child').addEventListener('click', () => {
console.log('子元素被點擊');
});
當我們點擊按鈕時,會看到:
子元素被點擊
父元素被點擊
這就是事件冒泡,事件從目標元素開始,向上冒泡到父元素。
傳統方法的問題
假設我們有一個列表,每個項目都需要點擊事件:
<ul id="list">
<li>項目 1</li>
<li>項目 2</li>
<li>項目 3</li>
<!-- 更多項目... -->
</ul>
傳統做法:
const items = document.querySelectorAll('#list li');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
function handleClick(event) {
console.log('點擊了:', event.target.textContent);
}
這種做法的問題:
- 性能問題:為每個元素都添加監聽器
- 記憶體消耗:大量的監聽器佔用記憶體
- 動態元素問題:新添加的元素沒有監聽器
使用事件代理
事件代理的做法:
const list = document.getElementById('list');
list.addEventListener('click', function(event) {
// 檢查點擊的是否為 li 元素
if (event.target.tagName === 'LI') {
console.log('點擊了:', event.target.textContent);
}
});
事件代理的優點
- 提升性能:只需要一個監聽器
- 節省記憶體:減少監聽器的數量
- 支援動態元素:新添加的元素自動擁有事件處理
- 簡化代碼:統一的事件處理邏輯
實際應用範例
範例 1:動態列表
<div id="todo-app">
<input type="text" id="new-item" placeholder="輸入新項目">
<button id="add-btn">添加</button>
<ul id="todo-list">
<li>
<span>學習 JavaScript</span>
<button class="delete-btn">刪除</button>
</li>
<li>
<span>學習 CSS</span>
<button class="delete-btn">刪除</button>
</li>
</ul>
</div>
const todoList = document.getElementById('todo-list');
const newItemInput = document.getElementById('new-item');
const addBtn = document.getElementById('add-btn');
// 使用事件代理處理刪除按鈕
todoList.addEventListener('click', function(event) {
if (event.target.classList.contains('delete-btn')) {
// 刪除項目
event.target.parentElement.remove();
}
});
// 添加新項目
addBtn.addEventListener('click', function() {
const text = newItemInput.value.trim();
if (text) {
const li = document.createElement('li');
li.innerHTML = `
<span>${text}</span>
<button class="delete-btn">刪除</button>
`;
todoList.appendChild(li);
newItemInput.value = '';
}
});
範例 2:表格操作
<table id="data-table">
<thead>
<tr>
<th>姓名</th>
<th>年齡</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>張三</td>
<td>25</td>
<td>
<button class="edit-btn" data-id="1">編輯</button>
<button class="delete-btn" data-id="1">刪除</button>
</td>
</tr>
<!-- 更多行... -->
</tbody>
</table>
const table = document.getElementById('data-table');
table.addEventListener('click', function(event) {
const target = event.target;
const id = target.dataset.id;
if (target.classList.contains('edit-btn')) {
console.log('編輯 ID:', id);
// 執行編輯邏輯
} else if (target.classList.contains('delete-btn')) {
console.log('刪除 ID:', id);
// 執行刪除邏輯
if (confirm('確定要刪除嗎?')) {
target.closest('tr').remove();
}
}
});
事件代理的注意事項
- 事件類型限制:只能用於支援冒泡的事件(如 click、keydown 等)
- 目標元素檢查:需要檢查 event.target 是否為預期的元素
- 阻止冒泡:如果在子元素中使用
event.stopPropagation(),事件不會冒泡到父元素
進階技巧
使用 matches() 方法
list.addEventListener('click', function(event) {
if (event.target.matches('li.item')) {
// 處理符合選擇器的元素
console.log('點擊了項目');
}
});
使用 closest() 方法
list.addEventListener('click', function(event) {
const item = event.target.closest('li');
if (item) {
// 處理最近的 li 祖先元素
console.log('點擊了項目:', item.textContent);
}
});
事件代理是一個強大的技術,能夠有效提升網頁性能,特別是在處理大量動態元素時。合理使用事件代理可以讓我們的代碼更加高效和易於維護。