Có mấy sự kiện bắn liên tù tì cả chục lần mỗi giây - scroll, resize, mousemove, rồi từng phím gõ trong ô search. Nếu chạy code nặng (gọi API, tính lại layout) mỗi lần như vậy là main thread với network ngộp thở luôn. Debounce và throttle là hai kỹ thuật giới hạn tần suất giải quyết chuyện này - mà chúng giải quyết hai vấn đề khác nhau.
Debounce: đợi hết bão rồi mới chạy
Hàm debounce trì hoãn việc chạy tới khi sự kiện ngừng bắn trong một khoảng thời gian nhất định. Mỗi sự kiện mới tới là reset lại đồng hồ đếm, nên nó chỉ chạy đúng một lần, sau khi mọi thứ im re.
Xài cái này khi mình chỉ quan tâm trạng thái cuối cùng:
- Search-as-you-type (gọi API sau khi người dùng ngừng gõ)
- Validate field sau khi người dùng sửa xong
- Autosave bản nháp khi ngừng chỉnh sửa
function debounce(fn, delay = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce((q) => fetch(`/api/search?q=${q}`), 300);
input.addEventListener('input', (e) => search(e.target.value));
Gõ nhanh chữ "laravel" thì chỉ một request được gửi đi - 300ms sau lần gõ cuối cùng.
Throttle: chạy đều đặn theo nhịp
Hàm throttle chạy tối đa một lần mỗi khoảng thời gian, bất kể sự kiện bắn bao nhiêu lần. Nó không đợi im lặng - nó đảm bảo chạy đều theo nhịp cố định.
Xài cái này khi mình muốn cập nhật định kỳ trong lúc sự kiện diễn ra liên tục:
- Cập nhật thanh tiến trình khi scroll
- Tính lại layout khi
resize - Giới hạn tốc độ click liên tục
function throttle(fn, interval = 200) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}
const onScroll = throttle(() => updateProgressBar(), 200);
window.addEventListener('scroll', onScroll);
Scroll liên tục 3 giây thì thanh tiến trình cập nhật tầm 15 lần (mỗi 200ms), chứ không phải chạy hết cả trăm sự kiện scroll.
Vậy xài cái nào?
Tự hỏi bản thân: mình muốn kết quả sau khi hoạt động lắng xuống, hay trong lúc nó đang diễn ra?
- "Sau khi lắng xuống" → debounce (search, autosave, validate).
- "Chạy đều trong lúc nó diễn ra" → throttle (scroll, resize, kéo thả).
Lên production thì nên xài mấy thư viện đã được kiểm chứng như lodash.debounce / lodash.throttle - tụi nó lo sẵn hết mấy vụ leading/trailing edge, cancel, với maxWait cho mình rồi.

