الخطوات
-
1
الفرق بين Debounce و Throttle
Debounce يؤجل التنفيذ حتى يصمت تدفق الأحداث لمدة N مللي ثانية. كل حدث جديد يُعيد ضبط المؤقت. استخدمه حين تهتم فقط بالقيمة النهائية — استعلامات البحث، وإعادة حسابات تغيير الحجم، والحفظ التلقائي.
Throttle يسمح للدالة بالتشغيل مرة واحدة كحد أقصى كل N مللي ثانية حتى لو استمرت الأحداث. استخدمه حين تحتاج تحديثات منتظمة أثناء التدفق — موضع التمرير، وتتبع الفأرة، وأشرطة التقدم.
-
2
اكتب دالة debounce المساعدة
الجوهر ثمانية أسطر: دالة تأخذ
fnوdelay، وتُعيد دالة مغلفة، وتستخدم معرّف مؤقت محفوظ في closure لإلغاء أي استدعاء معلق قبل جدولة استدعاء جديد.javascriptfunction debounce(fn, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; } -
3
طبّقها على حقل البحث
غلّف استدعاء الـ API بـ
debounceقبل إضافته كمستمع للحدث. سيُطلق البحث بعد 300 مللي ثانية فقط من توقف المستخدم عن الكتابة، لا عند كل ضغطة مفتاح.javascriptconst searchInput = document.getElementById('search'); async function fetchResults(query) { if (!query.trim()) return; const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`); const data = await res.json(); renderResults(data); } const debouncedSearch = debounce(fetchResults, 300); searchInput.addEventListener('input', (e) => { debouncedSearch(e.target.value); }); -
4
طبّقها على معالج تغيير حجم النافذة
تُطلق أحداث تغيير حجم النافذة باستمرار بينما يسحب المستخدم حافة المتصفح. تأجيل المعالج يعني تنفيذ حسابات التخطيط المكلفة مرة واحدة فقط، بعد اكتمال التغيير.
javascriptfunction recalculateLayout() { console.log('Recalculating at:', window.innerWidth, window.innerHeight); // ... DOM measurements, chart redraws, grid recalculations } window.addEventListener('resize', debounce(recalculateLayout, 200)); -
5
أضف خيار الحافة الأمامية (leading edge)
النسخة الافتراضية تُنفَّذ عند الحافة المتأخرة (بعد فترة الهدوء). لحماية أزرار الإرسال — حيث تريد تنفيذ الاستدعاء الأول فوراً وتجاهل التكرارات السريعة — استخدم نسخة الحافة الأمامية.
javascriptfunction debounce(fn, delay, { leading = false } = {}) { let timer; return function (...args) { const callNow = leading && !timer; clearTimeout(timer); timer = setTimeout(() => { timer = null; if (!leading) fn.apply(this, args); }, delay); if (callNow) fn.apply(this, args); }; } // Fires immediately on first click, ignores rapid repeat clicks const handleSubmit = debounce(submitForm, 500, { leading: true }); -
6
احتفظ بسياق `this` الصحيح
تستخدم الدالة المساعدة أعلاه
fn.apply(this, args)، مما يمرر قيمةthisالتي استُدعيت بها الدالة المغلفة. هذا مهم عند تأجيل توابع على كائنات أو نماذج من الـ class. لا تستخدم دالة سهمية كمغلف خارجي أبداً — فهي تربطthisلغوياً وستتجاهل سياق المستدعي بصمت.javascriptclass SearchWidget { constructor(input) { this.input = input; this.query = ''; // Arrow function here would lose `this` inside handleInput this.input.addEventListener('input', debounce(this.handleInput.bind(this), 300)); } handleInput(e) { this.query = e.target.value; this.fetch(); } } -
7
لا تحتاج lodash لهذا
دالة
_.debounceمن lodash مختبرة جيداً وتتعامل مع حالات الحافة كالإفراغ وإلغاء الاستدعاءات المعلقة. لكن إذا كان Debounce هو كل ما تحتاجه، فالدالة المساعدة ذات الثمانية أسطر أعلاه كافية — بلا اعتماديات، وبلا تكلفة على حزمة البناء، وأنت تعرف تماماً ما تفعله. اعتمد على lodash فقط إن كنت تستخدمه أصلاً، أو إن احتجت إلى واجهة flush/cancel.
نصائح ومحاذير
- 300 مللي ثانية مناسبة افتراضياً لحقول البحث؛ 150-200 مللي ثانية تبدو أسرع استجابةً لأحداث تغيير الحجم والتمرير.
- طبّق debounce دائماً في موضع الاستدعاء — أنشئ مغلفاً جديداً في كل مرة، لا تعدّل الدالة الأصلية في مكانها، وإلا ستشترك كل المستدعين في مؤقت واحد.
- إذا احتجت إلى إلغاء استدعاء مؤجل (مثلاً عند إزالة مكوّن)، اكشف تابع <code>cancel</code> يستدعي <code>clearTimeout(timer)</code>.
- في React، أنشئ الدالة المؤجلة باستخدام <code>useMemo</code> أو <code>useRef</code> لتجنب إعادة إنشائها عند كل تصيير.
- التأجيل لا يغني عن تحديد معدل الطلبات من جهة السيرفر — طبّق throttling دائماً على جانب الـ API أيضاً.
خاتمة
Debouncing من أعلى التقنيات تأثيراً للحفاظ على سرعة استجابة الواجهات الغنية بالأحداث. دالة مساعدة واحدة من ثمانية أسطر تُزيل استدعاءات API المكررة، وتمنع اضطراب التخطيط، وتجعل نيتك في الكود واضحة. اكتبها مرة واحدة، ضعها في utils.js، واستوردها أينما احتجت إليها.