Giới thiệu
Trong trình duyệt web, khi người dùng thực hiện một hành động nào đó (như nhấp chuột, di chuột), một sự kiện sẽ được kích hoạt. Mỗi phần tử HTML đều có khả năng xử lý sự kiện, mặc dù ban đầu chúng thường được đặt là null (không có sự kiện nào được gán).
Sự kiện Bong Bóng và Sự kiện Bắt là gì?
Sự kiện bong bóng (Event Bubbling) là quá trình lan truyền sự kiện từ phần tử mục tiêu lên trên đến phần tử gốc (window). Ngược lại, sự kiện bắt (Event Capturing) là quá trình lan truyền sự kiện từ phần tử gốc (window) xuống phần tử mục tiêu.
Thứ tự thực thi thông thường là: trước tiên thực hiện bắt, sau đó mới thực hiện bong bóng.
Sự kiện Bong Bóng
Sự kiện bong bóng xảy ra trong cả DOM0 và DOM2. Khi một phần tử và các phần tử tổ tiên của nó được gán cùng một sự kiện, khi phần tử mục tiêu được kích hoạt, các phần tử tổ tiên cũng sẽ thực thi sự kiện đó.
Ví dụ DOM0
window.onclick = function() {
console.log('window');
}
container1.onclick = function() {
console.log('container 1');
}
container2.onclick = function() {
console.log('container 2');
}
button.onclick = function() {
console.log('button');
}
// Khi nhấp vào button, sẽ hiển thị: button -> container 2 -> container 1 -> window
Ví dụ DOM2
container1.addEventListener('click', function() {
console.log('container 1');
}, false);
container2.addEventListener('click', function() {
console.log('container 2');
}, false);
button.addEventListener('click', function() {
console.log('button');
}, false);
// Khi nhấp vào button, sẽ hiển thị: button -> container 2 -> container 1
Lợi ích và Hạn chế của Bong Bóng
Lợi ích của bong bóng là có thể xác định được phần tử nguồn của sự kiện thông qua thuộc tính target của đối tượng sự kiện, giúp tối ưu hóa hiệu suất.
Tuy nhiên, bong bóng cũng có thể gây ra các vấn đề trong phát triển. Ví dụ:
let toggle = true;
button.onclick = function(event) {
if (toggle) {
popup.style.display = 'none';
} else {
popup.style.display = 'block';
}
toggle = !toggle;
}
document.onclick = function() {
popup.style.display = 'none';
toggle = false;
}
Vấn đề: Khi nhấp vào button để ẩn popup, sự kiện sẽ bong bóng lên document và ẩn popup ngay lập tức, khiến button không thể hiển thị popup lại.
Giải pháp: Ngăn chặn Bong Bóng
Có thể sử dụng stopPropagation() hoặc cancelBubble để ngăn chặn bong bóng.
let toggle = true;
button.onclick = function(event) {
if (toggle) {
popup.style.display = 'none';
} else {
popup.style.display = 'block';
}
toggle = !toggle;
event.stopPropagation(); // hoặc event.cancelBubble = true;
}
document.onclick = function() {
popup.style.display = 'none';
toggle = false;
}
Lưu ý: stopPropagation() là cách viết chuẩn của W3C, trong khi cancelBubble = true không phải là phương thức chuẩn nhưng được hầu hết các trình duyệt hỗ trợ.
Sự kiện Bắt
DOM0 không hỗ trợ bắt sự kiện, chỉ DOM2/3 mới có khả năng này. Tham số thứ ba trong addEventListener() xác định xem có bắt sự kiện hay không, mặc định là false (không bắt).
container1.addEventListener('click', function() {
console.log('container 1');
}, true);
container2.addEventListener('click', function() {
console.log('container 2');
}, true);
button.addEventListener('click', function() {
console.log('button');
}, true);
// Khi nhấp vào button, sẽ hiển thị: container 1 -> container 2 -> button
Mô hình Luồng Sự kiện
Khi một sự kiện được kích hoạt, nó thường trải qua ba giai đoạn: giai đoạn bắt (từ window xuống phần tử mục tiêu), giai đoạn mục tiêu, và giai đoạn bong bóng (từ phần tử mục tiêu lên window).
Giai đoạn mục tiêu thực thi theo thứ tự các hàm được gán, không phụ thuộc vào thứ tự bắt hoặc bong bóng.
container1.addEventListener('click', function() {
console.log('container 1 - capture');
}, true);
container2.addEventListener('click', function() {
console.log('container 2 - bubble');
}, false);
button.addEventListener('click', function() {
console.log('button - bubble');
}, false);
function anotherHandler() {
console.log('button - another handler');
}
button.addEventListener('click', anotherHandler, false);
container2.addEventListener('click', function() {
console.log('container 2 - capture');
}, true);
container1.addEventListener('click', function() {
console.log('container 1 - bubble');
}, false);
button.addEventListener('click', function() {
console.log('button - capture');
}, true);
// Khi nhấp vào button, thứ tự hiển thị sẽ là:
// container 1 - capture -> container 2 - capture -> button - capture -> button - bubble -> another handler -> button - bubble -> container 2 - bubble -> container 1 - bubble
DOM0 và DOM2
DOM0 là cách xử lý sự kiện truyền thống, sử dụng các thuộc tính bắt đầu bằng "on" (như onclick, onload). DOM0 không hỗ trợ bắt sự kiện.
DOM2 cung cấp hai phương thức để xử lý sự kiện: addEventListener() để gán và removeEventListener() để gỡ bỏ.
Gán sự kiện
/*
Tham số 1: Tên sự kiện không có "on"
Tham số 2: Hàm xử lý sự kiện
Tham số 3: Giá trị boolean, có bắt sự kiện không. Mặc định là false (không bắt)
*/
element.addEventListener("tên_sự_kiện", hàm_xử_lý, có_bắt_sự_kiện);
Gỡ bỏ sự kiện
/*
Tham số 1: Tên sự kiện không có "on"
Tham số 2: Hàm xử lý sự kiện cần gỡ bỏ
Tham số 3: Giá trị boolean, có bắt sự kiện không. Mặc định là false (không bắt)
*/
element.removeEventListener("tên_sự_kiện", hàm_xử_lý_cần_gỡ, có_bắt_sự_kiện);
Đối với Internet Explorer, có hai phương thức riêng:
attachEvent('tên_sự_kiện_có_on', hàm_xử_lý)
detachEvent('tên_sự_kiện_có_on', hàm_xử_lý)
Lưu ý: Tham số đầu tiên trong các phương thức của IE phải là tên sự kiện có "on".