事件代理是一種技術,您可以將單一事件監聽器附加到父元素上,以管理其子元素的事件。與其對每個子元素添加個別監聽器,不如讓父元素捕捉冒泡的事件並識別互動的來源。
它是如何運作的?
事件代理依賴兩個關鍵的 JavaScript 機制:
事件冒泡: 事件從目標元素傳播到 DOM 樹的根部。
event.target:
確定事件的來源元素。
<table style="margin: 0; padding: 0;">
<thead>
<tr>
<th>特性</th>
<th>說明</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>效能</b></td>
<td>減少事件監聽器的數量,節省記憶體並提高效率。</td>
</tr>
<tr>
<td><b>控制機制</b></td>
<td>自動管理動態新增的元素,而無需額外的監聽器。</td>
</tr>
<tr>
<td><b>記憶體管理</b></td>
<td>將事件處理邏輯集中在較少的代碼區域。</td>
</tr>
<tr>
<td><b>常見使用案例</b></td>
<td>在現代瀏覽器中普遍支持。</td>
</tr>
</tbody>
</table>
JavaScript 事件通過 DOM 遵循可預測的生命週期。理解這些階段對掌握代理至關重要:
1. 捕獲階段: 事件從根部開始向下傳遞到目標元素。
2. 目標階段: 事件在目標元素上激活。
3. 冒泡階段: 事件向上傳播回根部。
事件代理主要在冒泡階段運作。
場景 1:管理列表的點擊事件
與其對每個列表項添加監聽器:
const ul = document.querySelector("ul");
ul.addEventListener("click", (event) => {
if (event.target.tagName === "LI") {
console.log("點擊的項目:", event.target.textContent);
}
});
這個單一的監聽器管理所有 li
元素,甚至是那些動態添加的:
const ul = document.querySelector("ul");
ul.innerHTML += "<li>新項目</li>"; // 不需要新的監聽器。
<br>
場景 2:委派多種類型的事件
將事件代理與事件類型檢查結合使用:
document.querySelector("#container").addEventListener("click", (event) => {
if (event.target.matches(".button")) {
console.log("按鈕被點擊");
} else if (event.target.matches(".link")) {
console.log("連結被點擊");
}
});
<br>
場景 3:使用代理處理表單
document.querySelector("#form").addEventListener("input", (event) => {
if (event.target.matches("input[name='email']")) {
console.log("電子郵件已更新:", event.target.value);
} else if (event.target.matches("input[name='password']")) {
console.log("密碼已更新。");
}
});
這種方法確保任何動態添加的輸入字段自動得到處理。
1. 使用特定選擇器: 避免廣泛的匹配,以防止意外行為。使用 event.target.matches()
或 event.target.closest()
。
2. 避免過度代理: 對父元素過度委派太多事件可能會變得低效,如果父元素包含眾多子元素。
3. 優化條件邏輯: 組織您的條件以最小化不必要的檢查。
4. 限制或防抖事件: 對於 scroll
或 resize
等事件,使用限制策略以提高效能:
function throttle(callback, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
callback(...args);
lastTime = now;
}
};
}
document.addEventListener("scroll", throttle(() => console.log("已滾動!"), 200));
方面 | 直接事件處理 | 事件代理 |
---|---|---|
設定複雜性 | 需要多個監聽器。 | 單一監聽器處理多個事件。 |
動態元素 | 需要手動重新附加。 | 自動支持。 |
在大型 DOM 中的效能 | 隨著監聽器數量的增長而降低。 | 通過集中監聽器來實現效能。 |
可維護性 | 在多個地方散佈邏輯。 | 集中且乾淨。 |
React
雖然 React 抽象了 DOM 操作,但您可以在合成事件中看到代理的等效物。React 使用一個根監聽器來管理其虛擬 DOM 中的所有事件。
jQuery
jQuery 的 .on()
方法簡化了代理:
$(document).on("click", ".dynamic-button", function () {
console.log("按鈕被點擊:", $(this).data("id"));
});
1. 意外匹配
確保您的選擇器不會意外地匹配不相關的元素。使用特定的選擇器或 event.target.closest()
。
2. 防止事件冒泡
在某些情況下,您可能需要為特定元素停止冒泡:
document.querySelector("#container").addEventListener("click", (event) => {
if (event.target.matches(".prevent-bubble")) {
event.stopPropagation();
}
});
1. 基準測試
事件代理在大型 DOM 中減少了記憶體使用,但如果父元素處理過多事件,可能會引入延遲。
2. 開發者工具
使用瀏覽器開發者工具分析已附加的監聽器(在 Chrome 的控制台中使用 getEventListeners
):
getEventListeners(document.querySelector("#parent"))
focus
和 blur
,不會冒泡。改用 focusin
和 focusout
:document.addEventListener("focusin", (event) => {
console.log("聚焦到:", event.target);
});
document
。JavaScript 事件代理 是一種關鍵的優化策略,能有效地擴展到互動式應用中。透過集中事件處理、減少記憶體使用和提高可維護性,它使開發人員能夠構建穩健且高效的網頁應用程序。
給你的一個迷因(也許會有共鳴...)🙃🙃
原文出處:https://dev.to/shafayeat/mastering-javascript-event-delegation-3k2k