نحوه برخورد و استفاده از توابع در جاوااسکریپت بسیار جالب است. آنها بسیار انعطافپذیر هستند، یعنی ما میتوانیم توابع را به عنوان یک مقدار به یک متغیر اختصاص دهیم، آنها را به عنوان یک مقدار از یک تابع دیگر return کنیم و همینطور به عنوان یک آرگومان به یک تابع دیگر منتقل نماییم. در این مقاله، به این موضوع میپردازیم که توابع Callback و Higher Order چیست و چگونه در جاوااسکریپت کار میکنند.
توابع به عنوان آبجکتهای درجه یک در جاوااسکریپت تعریف میشوند زیرا با آنها همانند متغیرها رفتار میگردد.
این بدان معنی است که توابع در جاوااسکریپت میتوانند:
درک نحوه برخورد با توابع در جاوااسکریپت ضروری است. زیرا، آنها به عنوان یک بلاک سازنده برای درک توابع Callback و 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 است.
مجموعه دیگری از توابع داخلی که معمولاً مورد استفاده قرار میگیرند توابع 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 فقط به توابع داخلی ارائه شده توسط جاوااسکریپت محدود نمیشوند.
از آنجایی که توابع در جاوااسکریپت به عنوان آبجکتهای درجه یک در نظر گرفته میشوند، میتوانیم از این رفتار استفاده کنیم و توابع بسیار کارآمد با قابلیت استفاده مجدد بسازیم.
در مثالهای زیر، چند تابع میسازیم. آنها نام مشتری و یک پیام را میپذیرند و سپس آن اطلاعات را در کنسول چاپ میکنند.
در ابتدا یک تابع ساده داریم که هر دوی این کارها را انجام میدهد:
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 میباشد.
اکنون در ادامه این موضوع را بررسی میکنیم که چگونه یک تابع را داخل تابع دیگر فراخوانی میکنیم و چرا این کار را انجام میدهیم.
باید این موضوع را به یاد داشته باشیم که توابع 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 تابعی است که به متغیر موجود در 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ها چگونه کار میکنند.
getTwoNumbers
به عنوان تابعی تعریف میشود که دو پارامتر num1
و num2
را میگیرد.getTwoNumbers
، تابع دیگری را return میکند که یک تابع درونی به نام add
است.add
هنگام فراخوانی، مجموع num1
و num2
را محاسبه کرده و نتیجه را در کنسول نمایش میدهد.getTwoNumbers
، متغیری به نام addNumbers
ایجاد میکنیم و نتیجه فراخوانی getTwoNumbers (5، ۲)
را به آن تخصیص میدهیم. این کار به طور موثر closureای را ایجاد میکند که در آن addNumbers
اکنون مقادیر ۵
و ۲
را به عنوان num1
و num2
دریافت میکند.addNumbers()
را برای اجرای تابع داخلی add
فراخوانی میکنیم. از آنجایی که addNumbers
یک closure است، همچنان به مقادیر num1
و num2
دسترسی دارد که به ترتیب روی ۵
و ۲
تنظیم شده بودند. در آخر هم مجموع آنها را محاسبه کرده و ۷
را در کنسول نمایش میدهد.به تابع higher orderای که داشتیم برمیگردیم. تابع بازگشتی greetCustomer
به عنوان مقداری return میشود که آن را در متغیری به نامgreet
ذخیره میکنیم. انجام این کار باعث میشود تا متغیر greet
خود یک تابع باشد. به این معنی که میتوانیم آن را به عنوان یک تابع فراخوانی کرده و آرگومانهایی را برای نام و نام خانوادگی ارسال نماییم.
اکنون که درک عمیقی در مورد نحوه عملکرد توابع higher order داریم، در ادامه در مورد توابع 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.
دیدگاهها: