تابع callback در جاوااسکریپت

در جاوااسکریپت متدها و توابع higher orderای وجود دارد که یک تابع را به عنوان آرگومان می‌پذیرند. این توابع که به عنوان آرگومان برای توابع دیگر مورد استفاده قرار می‌گیرند توابع callback نامیده می‌شوند. در این مقاله قصد داریم تا با نوشتن یک نمونه تابع callback، با مفهوم و نحوه استفاده از آن آشنا شویم. همینطور در دوره آموزش جاوااسکریپت – مفاهیم پیشرفته این توابع به شکل کامل مورد بررسی قرار گرفته‌ است.

Callback در جاوااسکریپت چیست؟

callback تابعی است که به عنوان آرگومان یک تابع دیگر ارسال می‌شود.

یعنی این که تابع parent معمولاً  از هر نوع تابعی می‌تواند استفاده کند. اما تابع callback، برای استفاده در یک مورد خاص (یا تعداد محدودی از موارد) است که در آن تابع parent مورد استفاده قرار می‌گیرد.

چگونه یک تابع callback در جاوااسکریپت ایجاد کنیم؟

تابع callback مانند هر تابع دیگری در جاوااسکریپت ساخته می‌شود:

function callbackFunction () {
    
}

تفاوت بین تابع callback و توابع دیگر فقط در نحوه استفاده از آن است. یک تابع callback به طور خاص به این منظور ساخته شده است تا به عنوان آرگومان یک تابع دیگر استفاده شود.

function anyFunction(fun) {
    // ...
    fun(a, b, c);
    //...
}

anyFunction(callbackFunction);

بنابراین برای ایجاد یک callbackFunctionباید بدانیم که تابع parent چگونه از تابع callback استفاده می‌کند، چه آرگومان هایی را می‌گیرد و به چه ترتیبی آن‌ها را ارسال می‌کند.

مثالی از تابع callback

یک تابع higher order که در زبان جاوااسکریپ وجود دارد متد everyمی‌باشد. این متد یک متد آرایه است و از یک فراخوانی برای بررسی اینکه آیا تمام عناصر موجود در آرایه یک تست خاصی را پشت سر گذاشته‌اند یا نه استفاده می‌کند.

با نگاهی به مستندات مربوط به متد every می‌توانیم ببینیم که callback سه آرگومان ارسال می‌کند: یک المنت از آرایه، ایندکس آن المنت و کل آرایه.

بنابراین ظاهر تابع callback چیزی شبیه به این خواهد بود:

function callbackFunction(element, index, array) {
    // do something
}

توابع callback به همان اندازه که ما به آن‌ها نیاز داریم می‌توانند ساده یا پیچیده باشند.

نحوه نوشتن تابع callback در جاوااسکریپت

فرض کنید با آرایه‌ای از رشته‌ها کار می‌کنیم. باید بررسی کنیم که آرایه فقط شامل رشته‌هایی باشد که دقیقاً سه کاراکتر دارند، حروف بزرگ هستند، همه حروف مختلف را دربرمی‌گیرند و در داخل آرایه تکرار نمی‌شوند.

این یک مثال بسیار پیچیده است، اما تمرین بسیار خوبی به شمار می‌آید.

وقتی یک تابع ایجاد می‌کنیم که موارد زیادی بررسی می‌کند، می‌توانیم در هر زمان یک شرط را مورد بررسی قرار دهیم.

شرط اول این است که عنصر یک رشته باشد، بنابراین آن را اضافه می‌کنیم:

function callbackFunction(element, index, array) {
    
    // check that element is a string
  const isNotString = typeof element !== "string";
    // if it's not, end function
    if (isNotString) {return;}
    
}

سپس رشته‌ها باید همگی شامل ۳ کاراکتر که از حروف بزرگ تشکیل می‌شوند باشند.

می‌توانیم این سه شرط را به صورت جداگانه بررسی کنیم و یا این که می‌توانیم این کار را با یک عبارت منظم که دقیقاً آن سه مورد را بررسی می‌کند انجام دهیم.

چنین عبارت منظمی مانند این خواهد بود: /^[A-Z]{3}$/.

اکنون اجزای این عبارت منظم را باهم بررسی می‌کنیم:

  • کاراکترهای ^در ابتدا و $در پایان anchor هستند. این‌ها می‌گویند که رشته باید دقیقاً به همین شکل شروع شده و به پایان برسد. و اگر از هر دو استفاده کنیم در واقع رشته را محدود کرده‌ایم تا فقط و دقیقاً یک الگو را در عبارت منظم داشته باشد.
  • [A-Z]یک کلاس کاراکتر است که با هر کاراکتری از Aتا Zمطابقت دارد، بنابراین همه حروف بزرگ را شامل می‌شود.
  • {۳}یک شمارنده است و می‌گوید که مورد قبلی باید دقیقاً سه بار متوالی با این الگو مطابقت داشته باشد.

عبارت منظم توضیح داده شده در بالا معادل این عبارت منظم است:/^[A-Z][A-Z][A-Z]$/.

در این حالت به جای شمارنده {۳} کلاس [A-Z] را سه مرتبه نوشته‌ایم.

اکنون آن را به کد اضافه می‌کنیم:

function callbackFunction(element, index, array) {
    
    // check that element is a string
    const isNotString = typeof element !== "string";
    // if it's not, end function
    if (isNotString) {
        return;
    }
    
    // check that string is 3 characters long and only uppercase letters
    const isItThreeUpperCaseLetters = /^[A-Z]{3}$/.test(element);
    // otherwise, end function
    if (!isItThreeUpperCaseLetters) {
        return;
    }
    
}

اگر نخواهیم از عبارات منظم استفاده کنیم، می‌توانیم این کار بدون آن‌ها نیز انجام دهیم که در ادامه آن را بررسی کرده‌ایم.

سپس در مرحله بعد باید بررسی کنیم که آیا کاراکترها متفاوت هستند یا خیر.

سه کاراکتر وجود دارد که برای بررسی آن‌ها می‌توانیم از این روش استفاده کنیم: element[0] !== element[1] && element[0] !== element[2] && element[1] !== element[2].

همچنین می‌توانیم این کار را با حلقه نیز انجام دهیم. به عنوان مثال:

// with the outer loop, you get j, the first index to compare
for (let j = 0; j++; j < element.length) {
    // with the inner loop you get k, the second index to compare
    for (let k = j+1; k++; k < element.length) {
        // you compare the element at index j with the element at index k
        if (element[j] === element[k]) {
            // if they are equal return to stop the function
            return;
        }
    }
}

حلقه‌ای که در مثال بالا نوشیم با هر lengthای کار می‌کند و نیازی به بازنویسی آن برای موقعیت‌های مختلف ندارد.

آیا این روش دقیقاً همان نوشتن سه مقایسه است؟

در تکرار اول j=0و k=1است، بنابراین اولین مقایسه به شکل element[0] === element[1]می‌باشد. سپس k افزایش پیدا کرده و j=0و k=2می‌شود، بنابراین element[0] === element[2]می‌شود.

در این مرحله حلقه داخلی متوقف می‌شود و حلقه بیرونی (حلقه‌ای که jدارد) به تکرار بعدی می‌رود. این بار j=1و حلقه داخلی از k=j+1شروع می‌شود، بنابراین در k=2داریم element[1] === element[2].

اکنون حلقه داخلی به پایان رسیده است، حلقه بیرونی از j=1 به j=2می‌رود اما این بار حلقه داخلی شروع نمی‌شود زیرا k = j+1 = 3است و از شرط k < element.lengthحلقه عبور نمی‌کند.

function callbackFunction(element, index, array) {
    
    
    // check that element is a string
    const isNotString = typeof element !== "string";
    // if it's not, end function
    if (isNotString) {
        return;
    }
    
    
    // check that string is 3 characters long and only uppercase letters
    const isItThreeUpperCaseLetters = /^[A-Z]{3}$/.test(element);
    // otherwise, end function
    if (!isItThreeUpperCaseLetters) {
        return;
    }
    
    
    // check if all characters are different
    const allDifferentCharacters = element[0] !== element[1] && element[0] !== element[2] && element[1] !== element[2];
    // if not, return to stop the function
    if (!allDifferentCharacters) {
        return;
    }
    
    
    
}

سپس آخرین چیزی که باید آن را بررسی کنیم این است که داخل آرایه رشته‌های تکراری نداشته باشیم.

می‌توانیم از indexOfاستفاده کنیم تا بررسی کنیم که آیا مورد فعلی اولین بار می‌باشد که elementدر داخل آرایه دیده شده است یا خیر. برای انجام این کار باید به آرایه ارجاع دهیم. ما این را داریم، پارامتر arrayیکی از آرگومان‌هایی است که به تابع callback ارسال می‌شود.

اگر این اولین باری باشد که رشته در آرایه وجود دارد، خروجی indexOfمشابه indexخواهد بود.

پس اگر مقدار array.indexOf(element) === indexبرابر با trueباشد، به این معنی است که element برای اولین بار در آرایه در index وجود دارد. اگر falseباشد، یعنی یک رشته یکسان قبل از آن در آرایه وجود دارد.

در ادامه این مورد را هم به کد خود اضافه می‌کنیم و و اگر رشته مورد نظر ما از تمامی شرط‌ها عبور کرده باشد تابع می‌تواند در انتها مقدارد true برگرداند.

function callbackFunction(element, index, array) {
    
    
    // check that element is a string
    const isNotString = typeof element !== "string";
    // if it's not, end function
    if (isNotString) {
        return;
    }
    
    
    // check that string is 3 characters long and only uppercase letters
    const isItThreeUpperCaseLetters = /^[A-Z]{3}$/.test(element);
    // otherwise, end function
    if (!isItThreeUpperCaseLetters) {
        return;
    }
    
    
    // check if all characters are different
    const allDifferentCharacters = element[0] !== element[1] && element[0] !== element[2] && element[1] !== element[2];
    // if not, return to stop the function
    if (!allDifferentCharacters) {
        return;
    }
    
    
    // check if it's the first appearence of element inside the array
    const isItFirstAppearence = array.indexOf(element) === index;
    // if not, return to stop the function
    if (!isItFirstAppearence) {
        return;
    }
    
    
    return true;
}

اگر از عبارت منظم استفاده نکنیم چه اتفاقی می‌افتد؟

در کد بالا، برای بررسی سه مورد مختلف از یک عبارت منظم استفاده کردیم:/^[A-Z]{3}$/.

اما اگر نمی‌خواهیم با عبارت منظم کار کنیم می‌توانیم از ویژگی lengthبرای بررسی اینکه آیا یک رشته دقیقاً دارای طول مشخصی است یا نه استفاده کنیم. در این مورد element.length === 3بررسی می‌کند که اینکه آیا رشته مورد نظر ما دقیقا سه کاراکتر دارد یا خیر.

پس از آن باید بررسی کنیم که رشته فقط شامل حروف و آن هم حروف بزرگ باشد.

برای انجام این کار می‌توانیم از charCodeAtاستفاده کنیم. این روش کد اسکی کاراکتر را برمی‌گرداند و با دانستن اینکه حروف بزرگ دارای کدهای اسکی از ۶۵ تا ۹۰ هستند، می‌توانید بررسی کنیم که آیا در این رشته فقط حروف بزرگ وجود دارد یا خیر.

سه عدد برای بررسی وجود دارد: element.charCodeAt(0)، element.charCodeAt(1)و element.charCodeAt(2).  همه آن‌ها باید بین ۶۵ تا ۹۰ باشند. با این که اینجا فقط سه کاراکتر داریم اما می‌توانیم از یک حلقه نیز استفاده کنیم.

بنابراین مثال ما به صورت زیر خواهد بود:

for (let i = 0; i++; i < element.length) {
    // find the ASCII code of the character
    const code = element.charCodeAt(i);
    // check if it's outside of the range
    if (code < 65 || code > 90) {
        // if it is, return to stop the function
        return;
    }
}

اکنون آن را به تابع اضافه می‌کنیم:

function callbackFunction(element, index, array) {
    
    // check that element is a string
    const isNotString = typeof element !== "string";
    // if it's not, end function
    if (isNotString) {return;}
    
    // check that element has length string
    const hasLengthThree = element.length === 3;
    // if it has a different length, end function
    if (!hasLengthThree) {return;}
    
    // loop over the characters
  for (let i = 0; i++; i < element.length) {
        // find the ASCII code of the character
        const code = element.charCodeAt(i);
        // check if it's outside of the range
        if (code < 65 || code > 90) {
            // if it's outside the range, return and stop the function
            return;
        }
    } 
}

نحوه استفاده از مثال تابع callback

تابع callback را نوشته‌ایم. در این بخش بررسی می‌کنیم که چگونه می‌توانیم از آن استفاده کنیم.

anArray.every(callbackFunction);

همچنین می‌توانیم از متد everyکه در داخل callback وجود دارد استفاده کنیم، شاید callback به متد filter باشد.

همانطور که یک برنامه پیچیده‌تر می‌شود، احتمالاً از توابع callback بیشتری نیز استفاده خواهد کرد.

چرا باید از توابع callback در جاوااسکریپت استفاده کنیم؟

توابع callback یکی از ویژگی‌های ساده جاوااسکریپت است. این بدان معنی است که ما می‌توانیم یک تابع کلی داشته باشیم که کاری را انجام می‌دهد(مانند متد every که بررسی می‌کند آیا هر المنت در یک آرایه با شرایط خاصی مطابقت دارد یا خیر، filterکه المنت‌هایی را که با یک شرایط خاص مطابقت ندارند حذف می‌کند، replaceیک string method است که یک callback می‌گیرد تا نحوه جایگزینی بخش‌هایی از یک رشته را توضیح دهد و غیره) و یک تابع callback برای اضافه کردن مشخصات آن رفتار برای موقعیت خاص.

  • filterدر آن موقعیت، المنت‌های مشخص شده توسط callback را حذف می‌کند.
  • every بررسی می‌کند که همه المنت‌های در یک موقعیت، همانطور که توسط تابع callback مشخص شده است باشند.
  • replace قسمت‌هایی از رشته را در موقعیتی که در آن استفاده می‌شود، همانطور که توسط callback مشخص شده است جایگزین می‌کند.

توابع Higher order سطحی از انتزاع را به کد اضافه می‌کنند. ما نمی‌دانیم و نیازی وجود ندارد که بدانیم چگونه متد every هر المنت آرایه را بررسی کرده و تأیید می‌کند که همه آن‌ها تست‌های مشخص شده توسط callback را پشت سر گذاشته‌اند. فقط باید بدانیم که این متد یک تابع callback را برای این کار می‌پذیرد.

جمع‌بندی

توابع callback توابعی هستند که به عنوان آرگومان‌های توابع دیگر ارسال می‎‌شوند. در این مقاله سعی کردیم تا یک نمونه از تابع callback در جاوااسکریپت را باهم بنویسیم و در نهایت با نحوه استفاده از آن‌ها و مزایایی که دارند آشنا شویم.

دیدگاه‌ها:

افزودن دیدگاه جدید