آنچه در این مقاله میخوانید
آموزش پیادهسازی تابع Debounce در JavaScript
۲۴ آبان ۱۳۹۹

اگر بخواهیم توابع debounce را شرح دهیم میتوان گفت که آنها در دسته توابع higher-order قرار میگیرند و با استفاده از آنها میتوانیم سرعت فراخوانی تابع دیگری را محدود کنیم، مثلا منتظر بمانیم تا تایپ کردن کاربر تمام شود و سپس نتایج را به او نشان دهیم.
توابع higher-order، تابعی را به عنوان ورودی دریافت یا تابعی را return میکنند، اما تابع debounce هر دو کار را انجام میدهد. یکی از معمولترین استفادهها از تابع debounce برای ارسال argument به event listener یک المنت HTML است. برای درک بهتر فایده و ساختار آن، به مثال زیر دقت داشته باشید.

فرض کنید که شما تابعی با نام myFunc را در هر بار که چیزی در قسمت input تایپ میشود، فراخوانی میکنید اما بعد از مدتی تصمیم میگیرید که تجربه کاربری برنامه خود را تغییر دهید. مثلا میخواهید که تابع myFunc، دو ثانیه پس از تایپ آخرین کلمه اجرا شود. این همان جایی است که شما میتوانید از تابع debounce استفاده کنید. بنابراین میبایست بهجای تابع myFunc از تابع debounce خود در event listener استفاده کرده و سپس تابع myFunc را به عنوان یک ورودی به تابع debounce میدهید.
حال با استفاده از این تجربه کاربری جدید، تابع myFunc دو ثانیه پس از نوشتن آخرین کلمه در input اجرا میشود.
نحوه پیادهسازی تابع debounce
کدهای زیر همان ۷ خط کدی هستند که باید در پیادهسازی تابع debounce درک کنید.
function debounce( callback, delay ) {
let timeout;
return function() {
clearTimeout( timeout );
timeout = setTimeout( callback, delay );
}
}
با خط اول شروع میکنیم، یک تابع با نام debounce ایجاد کردهایم که دو ورودی با نامهای callback و delay دریافت میکند.
function debounce( callback, delay ) {
}
callback همان تابعی است که میخواهیم تعداد دفعات اجرای آن را محدود کنیم وdelay که بر حسب میلیثانیه محاسبه میشود همان زمانی است که باید قبل از اجرای مجدد تابع callback سپری شود.
function debounce( callback, delay ) {
let timeout;
}
در خط دوم، یک متغیر با نام timeout تعریف میکنیم. این متغیر جدید مقدار timeoutID را که از فراخوانی setTimeout بهدست میآید، در خود نگه میدارد.
function debounce( callback, delay ) {
let timeout;
return function() {
}
}
در خط سوم، یک تابع بینام را برمیگردانیم تا هم به متغیر timeout دسترسی داشته باشد و هم آرگومانهای جدید هر فراخوانی را دریافت کند. این همان جایی است که مفهوم closure اهمیت پیدا میکند.
> Closure یعنی یک تابع حتی بعد از خروج از اسکوپ اولیهاش، همچنان به متغیرهای اطراف خود دسترسی داشته باشد. در این مثال، تابع بازگشتی بعد از پایان اجرایdebounceهنوز به متغیرtimeoutدسترسی دارد و به همین خاطر میتواند تایمر قبلی را لغو یا دوباره تنظیم کند.
function debounce( callback, delay ) {
let timeout;
return function() {
clearTimeout( timeout );
}
}
در خط چهارم از متد clearTimeout که از mixin WindowOrWorkerGlobalScope نشعت گرفته، استفاده میکنیم. با این کار اطمینان حاصل میشود که هر بار تابع debounce اجرا میشود، مقدار timeout مجددا تنظیم شده و شمارش شروع میشود.
شما با استفاده ازWindowOrWorkerGlobalScope در زبان JavaScript میتوانید به متدهای شناخته شدهای مانند setTimeout، clearTimeout، setInterval، clearInterval و fetch دسترسی داشته باشید.
function debounce( callback, delay ) {
let timeout;
return function() {
clearTimeout( timeout );
timeout = setTimeout( callback, delay );
}
}
در خط پنجم به پایان پیادهسازی تابع debounce میرسیم. در این خط کد، چندین کار انجام میشود. اولین کار، اختصاص مقدار timeoutID با فراخوانی تابع setTimeout به متغیر timeout است که در خط دوم تعریف کرده بودیم. این کار به ما امکان میدهد که با فراخوانی تابع setTimeout یک reference به متغیر timeout بسازیم، بنابراین میتوانیم مقدار timeout را در هر بار فراخوانی تابع debounce، مجددا تنظیم کنیم.
کار بعدی فراخوانی تابع setTimeout است و با استفاده از این تابع میتوانید یک تاخیر در اجرای تابع callback ایجاد کنید.
از آنجا که ما از timeout استفاده میکنیم، تابع callback زمانی اجرا میشود که مقدار این متغیر به صفر برسد. به این صورت با هر بار فراخوانی تابع debounce، مقدار متغیر timeout مجددا به مقدار delay بازنشانی میشود و بدین صورت میتوانیم اجرای تابع myFunc را محدود کنیم.
خطهای ۶ و ۷ شامل براکتها هستند که مطمئنا نیازی به توضیح ندارند.
تا اینجا متوجه شدید که عملکرد تابع debounce به چه شکل است، در ادامه با استفاده از توضیحات قبل، یک مثال واقعی از کارکرد این تابع را بررسی خواهیم کرد.
قبل از دیدن کد، منطق debounce بسیار ساده است:
- هر بار رویداد رخ میدهد، یک تایمر جدید تنظیم میشود.
- اگر قبل از تمام شدن تایمر، رویداد دوباره تکرار شود، تایمر قبلی لغو میشود.
- فقط وقتی رویداد برای مدت مشخصی تکرار نشود، تابع اصلی اجرا میشود.
به همین خاطر debounce برای کاهش اجرای پشتسرهم توابع و کم کردن درخواستهای غیرضروری بسیار مفید است.
تفاوت Debounce و Throttle
debounce و throttle هر دو برای کنترل تعداد اجرای توابع استفاده میشوند، اما رفتارشان یکسان نیست:
- Debounce صبر میکند تا رویدادها متوقف شوند، سپس تابع را اجرا میکند.
- Throttle اجازه میدهد تابع در هر بازه زمانی مشخص، فقط یک بار اجرا شود.
برای مثال، در جستوجوی لحظهای معمولاً debounce انتخاب بهتری است، چون فقط بعد از توقف تایپ کاربر درخواست ارسال میشود. اما برای رویدادهایی مثل scroll و resize اغلب throttle مناسبتر است، چون میخواهیم تابع در طول رویداد هم هر چند وقت یکبار اجرا شود.
استفاده از تابع debounce در یک پروژه واقعی
ابتدا با استفاده از کدهای HTML، یک input ایجاد میکنیم:
<label for="myInput">Type something in!</label>
<input id="myInput" type="text">
در مرحله بعد، تابعی که میخواهیم هر زمان متنی در input وارد شد، اجرا شود را تعریف میکنیم:
function helloWorld() {
console.log("Hello World!")
}
سپس با استفاده از زبان JavaScript، به المنت input خود یک keyup event listener اضافه میکنیم:
const myInput = document.getElementById("myInput");
myInput.addEventListener(
"keyup",
debounce( helloWorld, 2000 )
);
در نهایت با استفاده از کدهای بالا، اگر از آخرین کلمهای که در input تایپ کردهایم حداقل ۲ ثانیه گذشته باشد، تابع helloWorld اجرا میشود.
مثال کاربردیتر برای جستوجو
در مثال قبلی فقط یک پیام در کنسول چاپ میشد. در عمل، معمولاً میخواهیم مقدار فیلد را بخوانیم و بعد از مکث کاربر، جستوجو را اجرا کنیم:
function search(event) {
console.log("Searching for:", event.target.value);
}
const myInput = document.getElementById("myInput");
myInput.addEventListener("input", debounce(search, 500));
در این حالت، شیء event به تابع search پاس داده میشود و چون در پیادهسازی debounce از ...args استفاده کردهایم، آرگومانها از بین نمیروند.
نکات مهم در استفاده از Debounce
برای استفاده درست از debounce این نکات مهم هستند:
- اگر تابع اصلی آرگومان میگیرد، پیادهسازی
debounceباید آن آرگومانها را پاس بدهد. - برای جستوجوی لحظهای، تاخیرهای بین
300تا500میلیثانیه معمولاً انتخاب مناسبی هستند. - اگر تاخیر خیلی زیاد باشد، رابط کاربری کند به نظر میرسد.
- اگر از فریمورکهایی مثل React استفاده میکنید، نباید در هر رندر یک
debounceجدید بسازید، چون تایمر قبلی از دست میرود.
جمعبندی پایانی
توابع debounce بسیار ساده و در عین حال قدرتمند هستند، همچنین میتوانند تاثیر قابل توجهی در اکثر برنامههای JavaScript داشته باشند. با اینکه مثال ما بسیار ساده بود اما سازمانهای بزرگ از توابع debounce برای افزایش عملکرد برنامههایشان استفاده میکنند.
اگر برنامه JavaScript شما بعد از این بهینهسازی آماده انتشار است، ببینید چطور میشود آن را روی یک سرور ابری بالا آورد.
راهاندازی پروژه روی هاست ابری با پرداخت ساعتی

