شرح Debounce في JavaScript: كيف تؤخر تنفيذ الدالة بكفاءة مع أمثلة عملية

دقائق القراءة: 5

ما هو Debounce في JavaScript؟

تُستخدم تقنية debounce في JavaScript للتحكم في عدد مرات استدعاء دالة معيّنة عند تكرار حدث ما بسرعة، مثل الكتابة داخل حقل البحث أو النقر المتكرر على زر. الفكرة الأساسية هي تأخير التنفيذ قليلاً، وعدم تشغيل الدالة إلا بعد توقف الحدث لفترة زمنية محددة.

هذه التقنية مفيدة جداً في تحسين الأداء وتقليل الاستدعاءات غير الضرورية، خصوصاً عندما تكون العملية المرتبطة بالحدث مكلفة، مثل إرسال طلب إلى الخادم أو حفظ البيانات تلقائياً أو تحديث واجهة الاستخدام بشكل متكرر.

شرح مفهوم debounce في جافاسكربت لتأخير تنفيذ الدوال وتحسين الأداء

أصل مصطلح debounce

يرجع هذا المصطلح إلى عالم الإلكترونيات. عند الضغط على زر في جهاز إلكتروني، مثل جهاز التحكم عن بُعد، قد لا تُسجَّل الضغطة مرة واحدة فقط بشكل فوري، بل قد يحدث ما يشبه الارتداد السريع للإشارة، فتصل عدة نبضات قصيرة إلى الدارة الإلكترونية خلال لحظة واحدة.

ولمنع احتساب الضغطة أكثر من مرة، تتجاهل الشريحة الإلكترونية أي إشارات إضافية لفترة زمنية قصيرة جداً بعد استقبال أول إشارة. هذا هو نفس المبدأ تقريباً عند استخدام debounce في البرمجة.

رسم توضيحي يشرح ارتداد الزر ومفهوم debounce في الإلكترونيات والبرمجة

لماذا نحتاج إلى debounce في تطبيقات الويب؟

في واجهات الويب الحديثة، قد يُطلِق المستخدم الحدث نفسه مرات كثيرة خلال فترة قصيرة جداً. وإذا تم تنفيذ الدالة المرتبطة بكل حدث مباشرة، فقد يؤدي ذلك إلى:

  • استهلاك موارد غير ضروري.
  • زيادة عدد الطلبات إلى الخادم.
  • ضعف أداء الصفحة.
  • تجربة استخدام مزعجة أو غير مستقرة.

لذلك يساعد debounce على تنفيذ الدالة مرة واحدة فقط بعد انتهاء سلسلة الأحداث المتتالية.

أمثلة شائعة على الاستخدام

  • اقتراحات البحث بعد توقف المستخدم عن الكتابة.
  • الحفظ التلقائي للنماذج بعد انتهاء التعديل.
  • منع تكرار الضغط على الأزرار خلال وقت قصير.
  • تقليل استدعاءات الأحداث مثل scroll وresize.

كيفية إنشاء دالة debounce في JavaScript

فيما يلي تنفيذ بسيط وعملي للدالة باستخدام صياغة ES6:

function debounce(func, timeout = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, timeout);
  };
}

function saveInput() {
  console.log('Saving data');
}

const processChange = debounce(() => saveInput());

كيف تعمل هذه الدالة؟

تعتمد الدالة debounce() على فكرتين أساسيتين:

  1. الاحتفاظ بمرجع لمؤقت عبر المتغير timer.
  2. إلغاء المؤقت السابق عند كل استدعاء جديد، ثم إنشاء مؤقت جديد.

عند وقوع الحدث لأول مرة، يتم جدولة تنفيذ الدالة بعد 300 مللي ثانية مثلاً. وإذا وقع الحدث مرة أخرى قبل انتهاء هذه المدة، يتم إلغاء الجدولة السابقة باستخدام clearTimeout() ثم إعادة العد من جديد.

النتيجة: لن تُنفَّذ الدالة إلا عندما يتوقف الحدث عن التكرار خلال المدة المحددة.

مثال تطبيقي على حقل إدخال

يمكن ربط الدالة بحقل نصي على النحو التالي:

<input type="text" onkeyup="processChange()" />

في هذا المثال، لن يتم تشغيل الدالة مع كل ضغطة مفتاح مباشرة، بل بعد أن يتوقف المستخدم عن الكتابة لمدة قصيرة.

مثال على زر

<button onclick="processChange()">Click me</button>

مثال على حدث نافذة

window.addEventListener("scroll", processChange);

يمكنك أيضاً استخدام الدالة مع أي عنصر أو وظيفة برمجية تتعرض لاستدعاءات متلاحقة.

شرح سلوك debounce خطوة بخطوة

لنفترض أن المستخدم يكتب داخل مربع بحث:

  1. يكتب الحرف الأول، فيُستدعى processChange.
  2. تُلغى أي جدولة سابقة عبر clearTimeout(timer).
  3. تتم جدولة الدالة saveInput() للتنفيذ بعد 300 مللي ثانية.
  4. إذا واصل المستخدم الكتابة قبل انتهاء المدة، تُلغى الجدولة السابقة وتبدأ مدة جديدة.
  5. يتكرر هذا السلوك حتى يتوقف المستخدم.
  6. عند التوقف، تبقى آخر جدولة فعّالة، فتُنفَّذ الدالة أخيراً.

هذا النمط ممتاز عندما تريد التعامل مع آخر حدث في سلسلة من الأحداث السريعة.

كيف نتعامل مع أول حدث ونتجاهل الباقي؟

في بعض الحالات، لا نريد انتظار آخر نقرة، بل نريد قبول أول نقرة فقط ثم تجاهل النقرات التالية لفترة قصيرة. هذا مفيد مثلاً لمنع إرسال النموذج أكثر من مرة أو لمنع الضغط المزدوج على زر الشراء.

في هذه الحالة يمكن استخدام نمط يُشغّل الدالة في البداية، ويمنع التكرار حتى انتهاء المهلة:

function debounce_leading(func, timeout = 300) {
  let timer;
  return (...args) => {
    if (!timer) {
      func.apply(this, args);
    }
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = undefined;
    }, timeout);
  };
}

ما الفرق بين هذا الأسلوب والأسلوب السابق؟

  • الإصدار التقليدي من debounce ينفّذ الدالة بعد توقف الأحداث.
  • إصدار debounce_leading ينفّذ الدالة فور أول حدث، ثم يمنع التكرار مؤقتاً.

لذلك يُستخدم كل نمط بحسب الغرض المطلوب من التفاعل.

متى تستخدم debounce ومتى لا تستخدمه؟

استخدمه عندما:

  • تريد تقليل عدد الاستدعاءات المتكررة.
  • ترغب في تحسين أداء عناصر البحث أو الفلاتر.
  • تنفّذ عمليات مكلفة مثل API calls أو الحفظ التلقائي.
  • تحتاج إلى منع التفاعل المكرر خلال وقت قصير.

قد لا يكون الخيار الأنسب عندما:

  • تحتاج إلى استجابة فورية مع كل حدث.
  • يكون المطلوب تنفيذ الدالة على فترات منتظمة، وهنا قد يكون throttle أنسب.

تنفيذات جاهزة في المكتبات الشهيرة

لا يلزم دائماً كتابة دالة debounce بنفسك، لأن كثيراً من مكتبات JavaScript الشهيرة توفر تنفيذات جاهزة وموثوقة.

المكتبة مثال الاستخدام
jQuery $.debounce(300, saveInput);
Lodash _.debounce(saveInput, 300);
Underscore _.debounce(saveInput, 300);

الاعتماد على مكتبة معروفة قد يكون خياراً جيداً في المشاريع الكبيرة، لأنه يوفّر وقت التطوير ويقلل احتمالات الأخطاء، خاصة عند الحاجة إلى خيارات إضافية مثل leading وtrailing والتحكم الدقيق في سلوك التنفيذ.

نصائح عملية لاستخدام debounce بكفاءة

  • اختر مدة زمنية مناسبة لطبيعة التفاعل، مثل 200 إلى 500 مللي ثانية.
  • لا تجعل التأخير طويلاً لدرجة تؤثر سلباً على تجربة المستخدم.
  • اختبر السلوك على الهاتف وسطح المكتب، لأن سرعة التفاعل تختلف بين الأجهزة.
  • إذا كنت تتعامل مع طلبات شبكة، ففكّر أيضاً في إلغاء الطلبات السابقة عند الحاجة.

الخلاصة التقنية

تُعد تقنية debounce من الأدوات الأساسية في تطوير الواجهات الحديثة، لأنها تعالج مشكلة شائعة تتمثل في تكرار الأحداث خلال زمن قصير. استخدامها الصحيح يرفع كفاءة التطبيق، ويقلل استهلاك الموارد، ويمنح المستخدم تجربة أكثر سلاسة. وإذا كنت تطور حقول بحث، أو أنظمة حفظ تلقائي، أو أزراراً حساسة للتكرار، فإن إضافة debounce ليست مجرد تحسين اختياري، بل خطوة تقنية ذكية وضرورية في كثير من السيناريوهات.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *