هذا المقال يتطلب مستوى متوسط. لو لسه مبتدئ، كمّل معايا؛ هنبدأ بمثال بسيط جدًا قبل الجزء التقني.
الفرق بين Debounce وThrottle: تحكّم في زحمة الأحداث بسطرين كود
لو صندوق البحث عندك بيبعت طلب للسيرفر مع كل حرف بيكتبه المستخدم، إنت بتضيّع أكثر من 90% من الطلبات على الفاضي. الحل مش سيرفر أقوى، الحل سطرين كود. المقال يوريك الفرق بين Debounce وThrottle، وإمتى تستخدم كل واحد بالظبط، بكود شغّال وأرقام مقاسة.
المشكلة باختصار
أحداث زي الكتابة (input) والتمرير (scroll) وتغيير حجم النافذة (resize) بتتنفّذ عشرات المرات في الثانية الواحدة. لو ربطت كل نبضة منها بعملية غالية — طلب شبكة، حساب معقّد، إعادة رسم — التطبيق بيغرق في شغل هو أصلًا مش محتاجه. النتيجة: بطء، وحمل زيادة على السيرفر، وفاتورة أعلى.
الأول مثال بسيط: المصعد وموظف الحضور
تخيّل الفرق كده. Debounce زي باب المصعد: ناس بتضغط زر الطابق ورا بعض بسرعة، والباب مش بيتحرك مع كل ضغطة. بيستنى لحد ما الضغط يهدأ تمامًا، وبعد ثانية سكوت يتحرك مرة واحدة. أي ضغطة جديدة بتصفّر العداد من الأول.
Throttle زي موظف بيسجّل الحضور مرة كل دقيقة بالظبط: مهما دخل ناس كتير في الدقيقة دي، هو بيسجّل تسجيلة واحدة، وبعدها يقفل لحد ما تعدّي الدقيقة. يعني تنفيذ منتظم بإيقاع ثابت، مش استنى-لحد-ما-يهدأ.
نفس الكلام بشكل علمي ودقيق
Debounce: أجّل تنفيذ الدالة لحد ما يعدّي فترة زمنية X من غير ما يحصل أي حدث جديد. كل حدث جديد بيلغي المؤقّت السابق ويبدأ عدّ من جديد. مناسب لـ "نفّذ لما المستخدم يخلّص".
Throttle: اسمح للدالة تتنفّذ مرة واحدة بحد أقصى كل فترة X، وتجاهل أي أحداث بتيجي جوه الفترة دي. مناسب لـ "نفّذ باستمرار لكن بمعدّل محكوم".
كود شغّال
// Debounce: ينفّذ بعد ما تهدأ الأحداث بمقدار wait
function debounce(fn, wait) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
};
}
// Throttle: ينفّذ مرة واحدة كل limit كحد أقصى
function throttle(fn, limit) {
let waiting = false;
return function (...args) {
if (waiting) return;
fn.apply(this, args);
waiting = true;
setTimeout(() => { waiting = false; }, limit);
};
}
// الاستخدام: صندوق بحث بيطلب النتائج بعد ما المستخدم يبطّل كتابة
const runSearch = debounce((q) => {
fetch(`/api/search?q=${encodeURIComponent(q)}`)
.then((r) => r.json())
.then(renderResults);
}, 300);
input.addEventListener('input', (e) => runSearch(e.target.value));السطر المهم في debounce هو clearTimeout(t): كل ضغطة بتلغي الطلب المؤجّل وتبدأ عدّ جديد. في throttle، القفل هو اللي بيمنع التكرار قبل انتهاء الفترة.