Event delegation là gắn một listener trên phần tử cha thay vì gắn cho từng phần tử con, rồi dựa vào event bubbling để biết ai vừa được bấm.
// KHÔNG delegation: 1000 listener cho 1000 dòng
document.querySelectorAll('.row').forEach((row) => {
row.addEventListener('click', handle)
})
// Delegation: MỘT listener cho cả bảng
table.addEventListener('click', (e) => {
const row = e.target.closest('.row') // closest xử lý cả khi bấm vào con bên trong
if (!row || !table.contains(row)) return
handle(row.dataset.id)
})Ba lợi ích:
1. Ít bộ nhớ hơn — một listener thay vì hàng nghìn.
2. Chạy đúng với nội dung thêm sau — dòng mới chèn vào không cần gắn listener lại.
3. Dọn dẹp đơn giản — gỡ một listener là xong.
e.target là nơi sự kiện phát sinh, e.currentTarget là nơi listener đang gắn — phân biệt được hai cái này là mấu chốt:
table.addEventListener('click', (e) => {
e.target // có thể là <span> bên trong ô
e.currentTarget // luôn là table
})Lưu ý 1: dùng e.target.classList.contains('row') sẽ trượt khi người dùng bấm vào phần tử con. Luôn dùng closest().
Lưu ý 2: focus, blur, mouseenter, mouseleave không bubble — delegation không áp dụng được. Thay bằng focusin/focusout và mouseover/mouseout.
Lưu ý 3: gắn lên document cho mọi thứ khiến mỗi cú click phải đi qua nhiều handler. Gắn vào container gần nhất là đủ.