وظيفة Debounce في JavaScript: تحسين الأداء وتجربة المستخدم بتأخير استدعاء الدوال

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

مقدمة إلى وظيفة Debounce في JavaScript

تُعد وظائف Debounce في JavaScript من الدوال عالية الترتيب (higher-order functions) التي تهدف إلى التحكم في معدل استدعاء دالة أخرى. الدالة عالية الترتيب هي دالة تأخذ دالة أخرى كمعامل، أو تُعيد دالة كجزء من ناتجها، أو كليهما. وظيفتنا debounce تقوم بالأمرين معًا.

الاستخدام الأكثر شيوعًا لوظيفة debounce هو تمريرها كمعامل إلى مستمع حدث (event listener) مرتبط بعنصر HTML. لفهم أفضل لكيفية عملها ولماذا هي مفيدة، دعونا نلقي نظرة على مثال عملي.

تخيل أن لديك دالة تُسمى myFunc يتم استدعاؤها في كل مرة تقوم فيها بكتابة شيء ما في حقل إدخال (input field). بعد مراجعة متطلبات مشروعك، تقرر أنك ترغب في تغيير هذه التجربة. بدلًا من ذلك، تريد أن يتم تنفيذ myFunc فقط عندما يمر ثانيتان على الأقل منذ آخر مرة قمت فيها بالكتابة. هنا يأتي دور debounce.

بدلًا من تمرير myFunc مباشرةً إلى مستمع الحدث، ستقوم بتمرير وظيفة debounce. ستقوم debounce بدورها بأخذ myFunc كمعامل، بالإضافة إلى قيمة التأخير (مثل 2000 مللي ثانية). الآن، في كل مرة تتفاعل فيها مع حقل الإدخال، سيتم تنفيذ myFunc فقط إذا مرت ثانيتان على الأقل منذ آخر مرة تم فيها استدعاء myFunc.

كيفية تطبيق وظيفة Debounce من الصفر

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

 function debounce ( callback, delay ) {
  let timeout;
  return function ( ) {
    clearTimeout ( timeout );
    timeout = setTimeout ( callback, delay );
  }
 }

1. تعريف الدالة والمعاملات

بدءًا من السطر الأول، قمنا بتعريف دالة جديدة تُسمى debounce. هذه الدالة الجديدة تأخذ معاملين:

  • callback: وهي أي دالة تحتاج إلى تحديد عدد مرات تنفيذها.
  • delay: وهو الوقت (بالمللي ثانية) الذي يجب أن ينقضي قبل أن يتمكن callback من التنفيذ مرة أخرى.
 function debounce ( callback, delay ) {
  // ...
 }

2. تهيئة المتغير timeout

في السطر الثاني، نقوم بتعريف متغير غير مهيأ يُسمى timeout. هذا المتغير الجديد سيحتفظ بقيمة timeoutID التي يتم إرجاعها عندما نستدعي setTimeout لاحقًا في دالة debounce الخاصة بنا.

 function debounce ( callback, delay ) {
  let timeout;
  // ...
 }

3. إرجاع دالة مجهولة واستخدام Closures

في السطر الثالث، نقوم بإرجاع دالة مجهولة (anonymous function). هذه الدالة المجهولة ستحتفظ بالوصول إلى المتغير timeout عبر مفهوم الـ closure، مما يضمن أننا نستطيع الوصول إليه حتى بعد انتهاء التنفيذ الأولي لدالة debounce.

الـ closure في JavaScript يحدث عندما تحتفظ دالة داخلية بالوصول إلى النطاق المعجمي (lexical scope) لدالتها الخارجية، حتى لو كانت الدالة الخارجية قد انتهت من التنفيذ. إذا كنت ترغب في معرفة المزيد عن الـ closures، يمكنك قراءة الفصل السابع من كتاب “You Don’t Know JS” للمؤلف Kyle Simpson.

 function debounce ( callback, delay ) {
  let timeout;
  return function ( ) {
    // ...
  }
 }

4. مسح المؤقت السابق بـ clearTimeout

في السطر الرابع، نقوم باستدعاء الدالة clearTimeout. تضمن هذه الخطوة أنه في كل مرة يتم فيها استدعاء وظيفة debounce، يتم إعادة تعيين المؤقت (timeout)، ويمكن أن يبدأ العد من جديد.

تُعد clearTimeout جزءًا من الواجهة البرمجية WindowOrWorkerGlobalScope في JavaScript، والتي تمنحنا الوصول إلى العديد من الدوال المعروفة مثل setTimeout و setInterval و clearInterval و fetch.

 function debounce ( callback, delay ) {
  let timeout;
  return function ( ) {
    clearTimeout ( timeout );
    // ...
  }
 }

5. إنشاء مؤقت جديد بـ setTimeout

في السطر الخامس، وصلنا إلى جوهر تطبيق دالة debounce. هذا السطر يقوم بعدة أمور:

  1. تعيين قيمة للمتغير timeout: يتم تعيين قيمة timeoutID التي يتم إرجاعها عند استدعاء setTimeout. هذا يسمح لنا بالإشارة إلى المؤقت الذي تم إنشاؤه لإعادة تعيينه في كل مرة يتم فيها استخدام دالتنا debounce.
  2. استدعاء setTimeout: هذا ينشئ مؤقتًا سيقوم بتنفيذ الدالة callback (المعامل الأول لدالتنا debounce) بمجرد انقضاء delay (المعامل الثاني).

نظرًا لأننا نستخدم مؤقتًا، لن يتم تنفيذ callback إلا إذا سمحنا للمؤقت بالوصول إلى الصفر. هذا هو المكان الذي يكمن فيه قلب وظيفتنا debounce، حيث أننا نعيد تعيين المؤقت في كل مرة يتم فيها استدعاء debounce. هذا ما يسمح لنا بالحد من معدل تنفيذ دالتنا الأصلية (مثل myFunc).

 function debounce ( callback, delay ) {
  let timeout;
  return function ( ) {
    clearTimeout ( timeout );
    timeout = setTimeout ( callback, delay );
  }
 }

السطران الأخيران يحتويان فقط على الأقواس المعقوفة لإغلاق الدوال، لذلك لن نتطرق إليهما. هذا هو كل ما في الأمر، هكذا تعمل دالة debounce داخليًا.

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

الآن، دعونا نُكمل مثالنا السابق من البداية. سنقوم بإنشاء حقل إدخال وربط مستمع حدث به باستخدام دالتنا debounce كأحد معاملاته.

1. إنشاء حقل الإدخال HTML

أولًا، نحتاج إلى إنشاء حقل إدخال بسيط في ملف HTML الخاص بنا:

 <label for="myInput">اكتب شيئًا هنا!</label>
 <input id="myInput" type="text">

2. تعريف دالة التنفيذ

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

 function helloWorld ( ) {
  console.log( "مرحبًا بالعالم!" );
 }

3. ربط Debounce بحدث لوحة المفاتيح

أخيرًا، نحتاج إلى تحديد حقل الإدخال الذي أنشأناه أعلاه وربط مستمع حدث من نوع keyup به. سنستخدم دالتنا debounce لتأخير استدعاء helloWorld:

 const myInput = document.getElementById( "myInput" );
 myInput.addEventListener( "keyup", debounce( helloWorld, 2000 ) );

وبهذا نكون قد أكملنا مثالنا العملي! في كل مرة نكتب فيها شيئًا في حقل الإدخال، سيتم تنفيذ helloWorld فقط إذا مرت ثانيتان على الأقل منذ آخر مرة قمنا فيها بالكتابة. يمكنك تجربة هذا المثال الحي على Repl.it.

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

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

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

اترك تعليقاً

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