مستوى المقال: مبتدئ — يكفيك معرفة JavaScript الأساسي و event listeners. زمن القراءة المتوقّع: 8 دقائق.
لو الـ search box في موقعك بيرسل طلب لـ API لكل حرف بيكتبه المستخدم، عندك مشكلة بتدفع تمنها كل شهر. كلمة "محمد" بـ 5 حروف + تصحيحات + backspace ممكن تطلع 12 طلب لطلب بحث واحد. على 47 ألف زائر يومياً، ده بيتحوّل لـ 4 مليون طلب بدل 470 ألف. Debounce و Throttle بسطرين JavaScript بيخفّضوا الرقم ده 92% من غير لمس باقي الكود.
Debounce و Throttle: ضبط معدّل تنفيذ الدوال في JavaScript
المشكلة باختصار
الافتراض في JavaScript إن كل event بينادي الـ handler. لو ربطت دالة بحث على input event، كل ضغطة على لوحة المفاتيح بتستدعيها. النتيجة: الباك إند بيستقبل عاصفة طلبات، الـ DB بتقعد تشتغل من غير لزوم، وفاتورة الـ Cloud بتطلع بدون فايدة فعلية للمستخدم.
الـ debounce و throttle مش تحسينات للـ UX بس. هما تكنيكتين أساسيتين بيخلّوا التطبيق يحترم موارد الشبكة والسيرفر.
المثال اللي بيوضّح كل حاجة: حارس البنك
تخيّل حارس واقف على باب خزنة في بنك. الزبائن بيدخلوا ويخرجوا كل ثانية. لو الحارس فتح الباب لكل واحد فيهم، الباب هيتعب وممكن يعطل. الحارس عنده طريقتين يضبط الموضوع:
طريقة Debounce: الحارس بيقول "هفتح الباب لما العميل يفضل واقف ساكن قدامي 800 مللي ثانية". لو فيه زحمة مستمرّة، الباب بيفضل قافل. الباب بيفتح بس لما الموقف يهدا.
طريقة Throttle: الحارس بيقول "هفتح الباب مرة كل ثانية بالضبط، مهما كانت الزحمة". الإيقاع ثابت ومش مرتبط بعدد العملاء.
الفرق المهم: Debounce بيستنى الهدوء قبل ما ينفّذ. Throttle بيلتزم بإيقاع ثابت ومش بيهتم بالزحام.
التعريف العلمي
Debounce: تقنية بتأجّل تنفيذ الدالة لحد ما يعدّي زمن ثابت delay بدون استدعاءات جديدة. أي استدعاء جديد بيعمل reset للـ timer من الصفر. الدالة بتتنفّذ مرة واحدة بعد آخر استدعاء بـ delay.
Throttle: تقنية بتسمح بتنفيذ الدالة مرة واحدة فقط في كل نافذة زمنية ثابتة interval. أي استدعاءات بتيجي في نفس النافذة بتتجاهل أو بتتأجّل لأقرب نافذة جاية.
كود Debounce في 7 سطور JavaScript
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// الاستخدام: search box
const search = debounce((query) => {
fetch(`/api/search?q=${query}`)
.then(r => r.json())
.then(renderResults);
}, 300);
input.addEventListener('input', (e) => search(e.target.value));