نحوه برخورد و استفاده از توابع در جاوااسکریپت بسیار جالب است. آن‌ها بسیار انعطاف‌پذیر هستند، یعنی ما می‌توانیم توابع را به عنوان یک مقدار به یک متغیر اختصاص دهیم، آن‌ها را به عنوان یک مقدار از یک تابع دیگر return کنیم و همینطور به عنوان یک آرگومان به یک تابع دیگر منتقل نماییم. در این مقاله، به این موضوع می‌پردازیم که توابع Callback و Higher Order چیست و چگونه در جاوااسکریپت کار می‌کنند.

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

توابع به عنوان آبجکت‌های درجه یک در جاوااسکریپت تعریف می‌شوند زیرا با آن‌ها همانند متغیرها رفتار می‌گردد.

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

درک نحوه برخورد با توابع در جاوااسکریپت ضروری است. زیرا، آن‌ها به عنوان یک بلاک سازنده برای درک توابع Callback و Higher Order در جاوااسکریپت، و روش کار آن‌ها عمل می‌کنند.

منظور از توابع Higher Order در جاوااسکریپت چیست؟

توابع Higher Order توابعی هستند که توابع دیگری را به عنوان آرگومان می‌گیرند و همچنین یک تابع را به عنوان مقدار بازگشتی return می‌کنند.

تعداد زیادی توابع Higher Order داخلی در جاوااسکریپت وجود دارد. ما برخی از آن‌ها را بررسی کرده و با نحوه عملکردشان آشنا خواهیم شد. همچنین در بخش‌های بعدی روش ساخت توابع Higher Order را نیز بررسی خواهیم کرد.

ابتدا، با چند نمونه از توابع Higher Order داخلی آشنا می‌شویم.

متدهای آرایه

متدهای آرایه معمولاً اولین توابع Higher Order هستند که یک توسعه‌دهنده در هنگام یادگیری جاوااسکریپت با آن‌ها مواجه می‌شود. این توابع عبارتند از: map، filter، forEach، find، findIndex، someو هر متد آرایه دیگری که توسط جاوااسکریپت ارائه شده است.

این متدها یا توابع آرایه اشتراکات زیادی باهم دارند، اما یکی از رایج‌ترین ویژگی‌ها این است که همه آن‌ها یک تابع را به عنوان آرگومان ورودی دریافت می‌کنند. در ادامه یک قطعه کد داریم که نحوه عملکرد متد آرایه forEachرا نشان می‌دهد:

const people = [
  { firstName: "Jack", year: 1988 },
  { name: "Kait", year: 1986 },
  { name: "Irv", year: 1970 },
  { name: "Lux", year: 2015 },
];

people.forEach(function (person) {
  console.log(person);
});

// Output:  Logs every person object in the array

باتوجه به قطعه کد بالا، می‌بینیم که متد forEach تابعی را به عنوان آرگومان ورودی می‌پذیرد که در هر تکرار روی آرایه، آن را فراخوانی می‌کند. بنابراین متد آرایه forEach یک تابع Higher Order است.

Eventهای Timer

مجموعه دیگری از توابع داخلی که معمولاً مورد استفاده قرار می‌گیرند توابع setIntervalو setTimeoutهستند که به عنوان eventهای timer در جاوااسکریپت شناخته می‌شوند.

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

در قطعه کد زیر نحوع کارکرد تابع setTimeoutرا می‌بینیم:

setTimeout(function () {
  console.log("This is a higher order function");
}, ۱۰۰۰);

// Output: "This is a higher order function" after 1000ms / 1 second

مثال بالا ابتدایی‌ترین مثال از نحوه عملکرد یک تابع setTimeoutمی‌باشد که تابع و یک مدت زمان بر حسب میلی ثانیه را دریافت کرده و پس از گذشت مدت زمان ارائه شده، تابع را اجرا می‌کند.

در نهایت عبارت This is a higher order functionپس از ۱۰۰۰ میلی ثانیه (۱ ثانیه) در کنسول چاپ می‌شود.

setInterval(function () {
  console.log("This is a higher order function");
}, ۱۰۰۰);

// Output: "This is a higher order function" after every 1000ms / 1 second

تابع setIntervalمانند متدهای آرایه، مشابه تابع setTimeoutاست اما از نظر عملکردی تفاوت‌هایی نیز وجود دارد. اما الگوی مشترکی که وجود دارد این است که هر دو این توابعی، تابعی را به عنوان یکی از پارامترهای خود قبول می‌کنند.

تفاوتی که وجود دارد این است setTimeoutتابع را پس از سپری شدن مدت زمان ارائه شده اجرا می‌کند، اما setIntervalهر ۱۰۰۰ میلی ثانیه (۱ ثانیه) یک بار، بارها و بارها این تابع را اجرا می‌کند.

نحوه ایجاد و استفاده از تابع Higher Order

توابع higher order فقط به توابع داخلی ارائه شده توسط جاوااسکریپت محدود نمی‌شوند.

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

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

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

function greetCustomer(firstName, lastName, salutation) {
  const fullName = `${firstName} ${lastName}`;

  console.log(`${salutation} ${fullName}`);
}

greetCustomer("Franklin", "Okolie", "Good Day");

// Output: "Good Day Franklin Okolie"

greetCustomer سه آرگومان را دریافت می‌کند: نام، نام خانوادگی و salutation(متن پیام). سپس یک پیام به مشتری را به کنسول چاپ می‌کند.

اما این تابع یک مشکل دارد، یعنی به طور هم‌زمان به جای انجام یک کار، دو کار را انجام می‌دهد: نوشتن نام کامل مشتری و همچنین چاپ متن پیام.

این کار روش درستی نیست زیرا، تابع باید یک کار را به بهترین شکل ممکن انجام دهد. بنابراین ما کد خود را دوباره اصلاح می‌کنیم.

به این صورت که یک تابع دیگری باید وجود داشته باشد تا نام مشتری را بنویسد و تابع greetCustomerفقط باید متن پیام را در کنسول چاپ نماید. به این ترتیب تابعی می‌نویسیم که این کار را مدیریت کند:

function composeName(firstName, lastName) {
  const fullName = `${firstName} ${lastName}`;

  return fullName;
}

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

function greetCustomer(composerFunc, firstName, lastName, salutation) {
  const fullName = composerFunc(firstName, lastName);

  console.log(`${salutation} ${fullName}`);
}

greetCustomer(composeName, "Franklin", "Okolie", "Good Day");

// Output: "Good Day Franklin Okolie"

اکنون این کد تمیزتر به نظر می‌رسد و هر تابع فقط یک کار را انجام می‌دهد. تابع greetCustomerاکنون چهار آرگومان را می‌پذیرد، و از آنجایی که یکی از آن آرگومان‌ها یک تابع است، بنابراین یک تابع higher order می‌باشد.

اکنون در ادامه این موضوع را بررسی می‌کنیم که چگونه یک تابع را داخل تابع دیگر فراخوانی می‌کنیم و چرا این کار را انجام می‌دهیم.

Return کردن یک تابع به عنوان value

باید این موضوع را به یاد داشته باشیم که توابع higher order یا یک تابع را به عنوان پارامتر می‌گیرند و یا این که یک تابع را به عنوان مقدار بازگشتی return می‌کنند.

در این بخش قصد داریم تا تابع greetCustomerرا مجدداً تغییر دهیم تا از آرگومان‌های کم‌تری استفاده کرده و یک تابع return کنیم:

function getGreetingsDetails(composerFunc, salutation) {
  return function greetCustomer(firstName, lastName) {
    const fullName = composerFunc(firstName, lastName);

    console.log(`${salutation} ${fullName}`);
  };

آخرین نسخه greetCustomerآرگومان‌های زیادی را پذیرفته است. چهار آرگومان زیاد نیست، اما اگر ترتیب آن‌ها را رعایت نکنیم مشکلات زیادی ایجاد خواهد شد. به طور کلی، هر چه تعداد آرگومان‌ها کم‌تر باشد، بهتر است.

بنابراین در مثال بالا، تابعی به نام getGreetingDetailsداریم که composerFuncو salutationرا از طرف تابع داخلی greetCustomerدریافت می‌کند. سپس تابع داخلی greetCustomerرا return می‌کند که خود، firstNameو lastNameرا به عنوان آرگومان می‌پذیرد.

با انجام این کار، به طور کلی تابع greetCustomerتعداد آرگومان‌های کم‌تری می‌گیرد.

اکنون نحوه استفاده از تابع getGreetingDetailsرا بررسی می‌کنیم:

const greet = getGreetingsDetails(composeName, "Happy New Year!");

greet("Quincy", "Larson");

// Output: "Happy New Year Quincy Larson"

بیایید نحوه عملکرد همه چیز تا این مرحله را با هم مرور کنیم. تابع higher order به نام getGreetingDetailsدو آرگومان دارد: یک تابع برای نوشتن نام و نام خانوادگی مشتری و یک salutation. سپس تابعی به نام greetCustomerرا return می‌کند که نام و نام خانوادگی مشتری را به عنوان آرگومان می‌پذیرد.

تابع greetCustomerکه return شده است نیز از آرگومان پذیرفته شده توسط getGreetingDetailsبرای اجرای برخی اقدامات استفاده می‌کند.

در این مرحله ممکن است سوالی مطرح شود مبنی بر این که چگونه یک تابع return شده می‌تواند از آرگومان‌های ارائه شده به یک تابع parent، به خصوص با توجه به نحوه عملکرد context اجرای تابع استفاده کند؟

این موضوع به دلیل closure امکان‌پذیر است. در ادامه با closure بیشتر آشنا می‌شویم.

Closure چیست؟

closure تابعی است که به متغیر موجود در scopeای که در آن ایجاد شده است دسترسی دارد. این دسترسی حتی پس از اینکه scope، دیگر در context اجرا وجود نداشته باشد هم برقرار است. این موضوع یکی از مکانیسم‌های اساسی توابع callback است. زیرا callbackها همچنان می‌توانند به متغیرهای ایجاد شده در یک تابع بیرونی پس از بسته شدن آن تابع بیرونی ارجاع داده و از آن استفاده کنند.

به عنوان مثال:

function getTwoNumbers(num1, num2) {
  return function add() {
    const total = num1 + num2;
    console.log(total);
  };
}

const addNumbers = getTwoNumbers(5, 2);

addNumbers();

//Output: 7;

کدی که در مثال بالا داریم تابعی به نام getTwoNumbersرا تعریف می‌کند و به ما نشان می‌دهد که closureها چگونه کار می‌کنند.

  1. getTwoNumbersبه عنوان تابعی تعریف می‌شود که دو پارامتر num1و num2را می‌گیرد.
  2. در داخل getTwoNumbers، تابع دیگری را return می‌کند که یک تابع درونی به نام addاست.
  3. تابع add هنگام فراخوانی، مجموع num1 و num2 را محاسبه کرده و نتیجه را در کنسول نمایش می‌دهد.
  4. خارج از تابع getTwoNumbers، متغیری به نام addNumbersایجاد می‌کنیم و نتیجه فراخوانی getTwoNumbers (5، ۲)را به آن تخصیص می‌دهیم. این کار به طور موثر closureای را ایجاد می‌کند که در آن addNumbersاکنون مقادیر ۵و ۲را به عنوان num1و num2دریافت می‌کند.
  5. در نهایت، addNumbers()را برای اجرای تابع داخلی addفراخوانی می‌کنیم. از آنجایی که addNumbersیک closure است، همچنان به مقادیر num1و num2دسترسی دارد که به ترتیب روی ۵و ۲تنظیم شده بودند. در آخر هم مجموع آن‌ها را محاسبه کرده و ۷را در کنسول نمایش می‌دهد.

به تابع higher orderای که داشتیم برمی‌گردیم. تابع بازگشتی greetCustomerبه عنوان مقداری return می‌شود که آن را در متغیری به نامgreetذخیره می‌کنیم. انجام این کار باعث می‌شود تا متغیر greet خود یک تابع باشد. به این معنی که می‌توانیم آن را به عنوان یک تابع فراخوانی کرده و آرگومان‌هایی را برای نام و نام خانوادگی ارسال نماییم.

اکنون که درک عمیقی در مورد نحوه عملکرد توابع higher order داریم، در ادامه در مورد توابع callback صحبت خواهیم کرد.

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

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

در مثال زیر، دوباره تابع setTimeoutرا داریم:

setTimeout(function () {
  console.log("This is a higher order function");
}, ۱۰۰۰);

// Output: "This is a higher order function" after 1000ms / 1 seconds

ما ثابت کردیم که تابع setTimeout یک تابع higher order است زیرا تابع دیگری را به عنوان آرگومان دریافت می‌کند.

تابعی که به عنوان آرگومان به تابع setTimeout ارسال می‌شود، تابع callback نام دارد. این موضوع به این دلیل است که در داخل تابع higher orderای که به آن منتقل شده است فراخوانی یا اجرا می‌گردد.

برای درک بهتر توابع callback، نگاهی دیگر به تابع greetCustomerکه از قبل داشتیم می‌اندازیم:

// THIS IS A CALLBACK FUNCTION
// IT IS PASSED AS AN ARGUMENT TO A FUNCTION

function composeName(firstName, lastName) {
  const fullName = `${firstName} ${lastName}`;

  return fullName;
}

// THIS IS A HIGHER ORDER FUNCTION
// IT ACCPEPTS A FUNCTION AS A ARGUMENT

function greetCustomer(composerFunc, firstName, lastName, salutation) {
  const fullName = composerFunc(firstName, lastName);

  console.log(`${salutation} ${fullName}`);
}

greetCustomer(composeName, "Franklin", "Okolie", "Good Day");

// Output: "Good Day Franklin Okolie"

composeName یک تابع callback است که به عنوان آرگومان به تابع greetCustomerکه یک تابع higher order ارسال شده و در داخل این تابع اجرا می‌شود.

تفاوت بین توابع Higher Order و توابع Callback

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

جمع‌بندی

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