نمونههایی از کاربرد توابع Callback
در بخش قبلی با توابع Callback آشنا شدیم. همچنین نمونهای از کاربرد این نوع توابع را در مرتبسازی آرایهها با متد sort بررسی کردیم. در جاوا اسکریپت متدهای زیاد دیگری نیز وجود دارد که مانند متد sort، از توابع Callback به عنوان ورودی استفاده میکنند. با توجه به اهمیت این مبحث و کاربرد زیاد توابع Callback در جاوا اسکریپت، قبل از به پایان رساندن این فصل، در این بخش با چند نمونهی دیگر از این نوع متدها و کاربرد آنها آشنا خواهید شد.
متد forEach
در فصل سوم دیدیم که برای اجرای تعدادی دستور بر روی تمام عناصر یک آرایه، میتوان از ساختارهای تکرار مانند ساختار for یا ساختار for-of استفاده کرد. روش دیگر برای انجام همین کار، استفاده از متد forEach است. این متد یک تابع Callback را به عنوان ورودی دریافت میکند و تمام عناصر آرایه را یکی یکی به این تابع ارسال میکند. این تابع نیز میتواند هر عمل دلخواهی را بر روی این عناصر انجام دهد.
فرض کنید هدف، چاپ کردن مقدار تمام عناصر آرایه در کنسول باشد. برای این کار میتوان از تابع زیر به عنوان آرگومان ورودی متد forEach استفاده کرد.
function print(value){
console.log(value);
}
تابع فوق هر مقداری که به عنوان ورودی دریافت میکند، در کنسول نمایش میدهد. پس میتوان همین تابع را به متد forEach ارسال کرد تا عناصر آرایه یکی یکی به این تابع ارسال شده و در کنسول نمایش داده شوند. قطعه کد زیر چنین کاری را انجام میدهد.
let colors = ['Red' , 'Green' , 'Blue'];
colors.forEach(print);
← Red
← Green
← Blue
همچنین جهت کم شدن حجم کدها، میتوان تابع print را با یک Arrow Function جایگزین کرد.
let colors = ['Red' , 'Green' , 'Blue'];
colors.forEach(value => console.log(value));
← Red
← Green
← Blue
این برنامه را میتوانید اینجا اجرا کنید.
لازم به ذکر است که تابعی که به متد forEach ارسال میشود، میتواند دو ورودی دریافت کند که ورودی دوم اختیاری است. در صورت استفاده از دو ورودی در تابع Callback، ورودی اول مقدار یکی از عناصر آرایه و ورودی دوم اندیس همان عنصر از آرایه خواهد بود. در مثال زیر علاوه بر مقدار عناصر آرایه، اندیس آنها نیز در کنسول نمایش داده میشود.
let colors = ['Red' , 'Green' , 'Blue'];
colors.forEach((value , index) => console.log(`Color at position ${index} is ${value}`));
← Color at position 0 is Red
← Color at position 1 is Green
← Color at position 2 is Blue
این برنامه را نیز میتوانید اینجا اجرا کنید. توجه کنید که در این مثال برای چاپ رشته در کنسول، از Template Literal استفاده شده است که در فصل دوم با این روش برای تعریف رشتهها آشنا شدیم.
متد map
متد map رفتاری بسیار مشابه رفتار متد forEach دارد. یعنی یک تابع Callback را به عنوان ورودی دریافت میکند و به ازای هر یک از عناصر آرایه، یک بار تابع Callback را فراخوانی میکند. تفاوت تابعی که به متد map ارسال میشود، با تابعی که به متد forEach ارسال میشود در این است که این تابع به ازای هر یک از عناصر آرایه، باید یک مقدار جدید را بازمیگرداند. در واقع متد map یک آرایهی جدید را به عنوان خروجی باز میگرداند. عناصر این آرایهی جدید همان مقادیری هستند که توسط تابع Callback بازگردانده شدهاند.
به عنوان مثال فرض کنید میخواهیم تمام عناصر یک آرایهی عددی را به توان دو برسانیم. در این صورت تابع Callback باید آرگومان ورودی خود را به توان دو رسانده و بازگرداند. قطعه کد زیر این کار را انجام میدهد.
let numbers = [2 , 3 , 4 , 5];
let result = numbers.map(value => value * value);
console.log(result);
← [4 , 9 , 16 , 25]
به عنوان مثالی دیگر، فرض کنید میخواهیم تمام حروف به کار رفته در تمام عناصر یک آرایه را به حروف بزرگ تبدیل کنیم. میدانیم که متد toUpperCase میتواند این کار را بر روی یک رشته انجام دهد. حال برای این که همین کار را بر روی تمام عناصر یک آرایه انجام دهیم، میتوان از متد map و یک تابع Callback به صورت زیر استفاده کرد.
let colors = ['Red' , 'Green' , 'Blue'];
let result = colors.map(value => value.toUpperCase());
console.log(result);
← ["RED" , "GREEN" , "BLUE"]
تابعی که به متد map ارسال میشود، در مجموع میتواند ۳ آرگومان ورودی دریافت کند آرگومانهای دوم و سوم اختیاری هستند. در صورت استفاده از آرگومانهای دوم و سوم، آرگومان دوم اندیس عنصر و آرگومان سوم خود آرایه را به تابع Callback ارسال میکند. البته آرگومان سوم به ندرت به کار برده میشود. مثال زیر نحوهی استفاده از یک تابع Callback با ۳ آرگومان ورودی را نشان میدهد.
let colors = ['Red' , 'Green' , 'Blue'];
let result = colors.map((value , index , array) => `Element ${index} is ${value}. There are ${array.length} items in total.`);
console.log(result);
← ["Element 0 is Red. There are 3 items in total." ,
"Element 1 is Green. There are 3 items in total." ,
"Element 2 is Blue. There are 3 items in total."]
مشاهده میکنید که در عناصر آرایهی خروجی، عدد ۳ مشترک است. چرا که در هر ۳ بار فراخوانی تابع Callback، آرایهی اولیه به این تابع ارسال میشود و مقدار خاصیت length نیز در هر فراخوانی یکسان و برابر با ۳ است. این برنامه را میتوانید اینجا اجرا کنید.
متد reduce
متد reduce یک آرایه را به یک مقدار واحد (Single Value) تبدیل میکند. مثلاً فرض کنید آرایهای از اعداد در اختیار دارید و میخواهید مجموع تمام اعداد موجود در آرایه را محاسبه کنید. در این صورت آرایه را به یک مقدار عددی تبدیل کردهاید. یا به عنوان مثالی دیگر فرض کنید قصد دارید تمام عناصر یک آرایه را به یکدیگر الحاق کرده و به یک رشته تبدیل کنید. در چنین مواردی میتوان از متد reduce استفاده کرد.
متد reduce یک تابع Callback را به عنوان ورودی دریافت میکند که این تابع Callback باید حداقل ۲ آرگومان ورودی دریافت کند. در صورتی که تعداد عناصر آرایه برابر با n باشد، این متد در حالت پیشفرض تابع Callback را n - 1 بار فراخوانی خواهد کرد. در اولین فراخوانی، ۲ عنصر اول آرایه به تابع Callback ارسال میشوند. در فراخوانی بعدی، مقدار بازگردانده شده از تابع Callback در فراخوانی اول، به همراه عنصر سوم آرایه به تابع Callback ارسال میشوند. در فراخوانی بعدی نیز مقدار بازگردانده شده از تابع Callback در فراخوانی دوم، به همراه عنصر چهارم آرایه به تابع Callback ارسال میشوند. و این روند تا رسیدن به آخرین عنصر آرایه ادامه پیدا میکند.
به عنوان مثال فرض کنید قصد داریم مجموع مقادیر موجود در یک آرایهی عددی را محاسبه کنیم. مثال زیر این کار را انجام میدهد.
let numbers = [2 , 7 , 11 , 8 , 9];
let total = numbers.reduce((prev , value) => prev + value);
console.log(total);
← 37
در مثال فوق، در مرحلهی اول اعداد ۲ و ۷ به تابع Callback ارسال میشوند و مجموع آنها یعنی مقدار ۹ توسط تابع Callback بازگردانده میشود. در مرحلهی بعدی اعداد ۹ و ۱۱ به تابع Callback ارسال شده و مقدار ۲۰ بازگردانده میشود. در مرحلهی بعدی اعداد ۲۰ و ۸ ارسال شده و مقدار ۲۸ بازگردانده میشود. در آخرین مرحله نیز اعداد ۲۸ و ۹ ارسال شده و مجموع آنها، یعنی ۳۷ از تابع Callback بازگردانده میشود که این مقدار به عنوان نتیجهی نهایی از متد reduce بازگردانده میشود. این برنامه را میتوانید اینجا اجرا کنید.
به عنوان مثالی دیگر به قطعه کد زیر توجه کنید که تمام عناصر یک آرایه را به صورت یک رشته به یکدیگر الحاق میکند.
let words = ['I' , 'am' , 33 , 'years' , 'old'];
let result = words.reduce((prev , value) => prev.concat(' ' , value));
console.log(result);
← "I am 33 years old"
توجه کنید که در چنین مواردی برای الحاق رشتهها بهتر است از متد concat به جای عملگر "+" استفاده شود. زیرا ممکن است در شرایط خاصی هر دو عملوند از نوع عددی باشند که در این صورت عمل جمع ریاضی انجام خواهد شد. اما در صورت استفاده از متد concat در هر صورت عمل الحاق انجام میشود.
همچنین میتوان آرگومان دومی را به متد reduce ارسال کرد. در این صورت در اولین فراخوانی تابع Callback، به جای ارسال ۲ عنصر اول آرایه به این تابع، این آرگومان جدید به همراه اولین عنصر آرایه به تابع Callback ارسال میشوند. از این آرگومان معمولاً برای مشخص کردن مقدار اولیه در محاسبات استفاده میشود. همچنین در این حالت برخلاف مثالهای قبلی، تعداد فراخوانیهای تابع Callback به جای n - 1، برابر با n خواهد بود.
مثلاً فرض کنید آرایهای از رشتهها را در اختیار داریم و میخواهیم مجموع کل کاراکترهای موجود در تمام رشتهها را محاسبه کنیم. برای این منظور میتوان به روش زیر عمل کرد. در این مثال مقدار اولیهی صفر برای مجموع کاراکترها ارسال میشود.
let words = ['I' , 'am' , 33 , 'years' , 'old'];
let result = words.reduce((total , value) => total + String(value).length , 0);
console.log(result);
← 13
در مثال فوق، در هر فراخوانی مقدار خاصیت length یکی از عناصر آرایه با مجموع مقادیر قبلی که در total ذخیره شده است جمع میشود. برای این که متغیر total در اولین فراخوانی مقدار صفر داشته باشد، مقدار صفر را به عنوان آرگومان دوم به متد reduce ارسال کردهایم. همچنین توجه کنید که قبل از استفاده از خاصیت length، با استفاده از تابع String، مقدار موجود در متغیر value به رشته تبدیل شده است. زیرا ممکن است برخی از عناصر آرایه از نوع رشته نباشند. مانند عنصر سوم که یک مقدار عددی است و مستقیماً نمیتوان تعداد کاراکترهای آن را با خاصیت length محاسبه کرد. پس باید تمام عناصر ابتدا به نوع رشته تبدیل شوند.
متد filter
یکی ار متدهای بسیار مفید در رابطه با آرایهها، متد filter است. با استفاده از این متد میتوان عناصری از آرایه که دارای شرایط خاصی هستند را حفظ کرد و بقیهی عناصر را حذف کرد. مثلاً فرض کنید میخواهیم عناصر یک آرایه را که اعدادی زوج هستند را حفظ کرده و بقیه را حذف کنیم. یا به عنوان مثالی دیگر فرض کنید میخواهیم عناصری از آرایه که تعداد کاراکترهایشان بیش از ۵ کاراکتر است را حفظ کرده و بقیه را حذف کنیم.
برای استفاده از این متد باید یک تابع Callback را به آن ارسال کرد. این تابع به ازای هر یک از عناصر آرایه یک بار فراخوانی میشود و باید یک مقدار Boolean را بازگرداند. اگر مقدار بازگردانده شده true باشد، عنصر ارسال شده در آرایه باقی میماند. و اگر مقدار بازگردانده شده false باشد، عنصر ارسال شده از آرایه حذف میشود. به عنوان مثال برنامهی زیر عناصری که عددی زوج باشند را حفظ کرده و بقیهی عناصر را حذف میکند.
let elements = ['green' , 22 , 11 , 5 , 12 , 24 , ' blue'];
let result = elements.filter( value => value % 2 === 0);
console.log(result);
← [22 , 12 , 24]
در مثال فوق برای تعیین زوج بودن هر یک از عناصر آرایه، باقی ماندهی تقسیم آن عنصر بر عدد ۲ با صفر مقایسه میشود. در صورتی که باقی مانده صفر باشد، نتیجهی مقایسه true بوده و عنصر مورد نظر در آرایه باقی میماند. در غیر این صورت نیز مقدار بازگشتی false بوده و عنصر مورد نظر از آرایه حذف میشود. این مثال را میتوانید اینجا اجرا کنید.
نکتهی بسیار مهمی که باید به آن توجه شود این است که در تمام متدهای معرفی شده در این بخش، آرایهی اولیه بدون تغییر باقی میماند و خروجی این متدها باید در متغیر دیگری ذخیره شود. مثلاً در مثال فوق آرایهی elements هیچ تغییری نمیکند. بلکه تغییرات مورد نظر به عنوان یک آرایهی جدید بازگردانده میشود که در این مثال در متغیر result ذخیره میشود.
استفاده از متدها به صورت زنجیرهای (Chaining)
به کار بردن زنجیرهای (Chaining) متدها در جاوا اسکریپت از کارهای بسیار معمول است. منظور از Chaining استفاده از خروجی یک متد به عنوان ورودی متد دیگر است. به عنوان مثال ورودی تمام متدهایی که در این بخش دیدیم، یک آرایه است. حال اگر خروجی یک متد یک آرایه باشد، میتواند به صورت زنجیرهای با این متدها به کار برده شود.
مثلاً فرض کنید قصد داریم مجموع مربعات عناصر یک آرایهی عددی را محاسبه کنیم. در همین بخش دیدیم که برای محاسبهی مربعات (توان دوم) عناصر یک آرایه، میتوان از متد map به همراه یک تابع Callback استفاده کرد. همچنین دیدیم که برای محاسبهی مجموع عناصر یک آرایهی عددی نیز میتوان از متد reduce استفاده کرد.
با توجه به اینکه خروجی متد map یک آرایه است، میتوان از متدهایی که بر روی آرایهها عمل میکنند (مانند reduce)، بر روی خروجی این متد به صورت زنجیرهای استفاده کرد. برای خوانایی بیشتر در این مثال از توابع بینام استفاده نمیکنیم. توابعی که برای این دو متد به عنوان تابع Callback به کار میبریم به صورت زیر هستند.
const square = value => value * value;
const sum = (prev , value) => prev + value;
حال میتوان برای محاسبهی مجموع مربعات عناصر یک آرایهی عددی به صورت زیر عمل کرد و متدهای map و reduce را به صورت زنجیرهای به کار برد.
let numbers = [1 , 2 , 3 , 4 , 5];
let total = numbers.map(square).reduce(sum);
console.log(total);
← 55
برنامه فوق را میتوانید اینجا اجرا کنید.
در جاوا اسکریپت متدهای زیاد دیگری نیز وجود دارند که به عنوان پارامتر ورودی از توابع Callback استفاده میکنند. در ادامهی این کتاب با موارد بسیار دیگری نیز آشنا خواهید شد. اما در مورد متدهایی که با آرایهها در ارتباط هستند، علاوه بر ۴ متدی که در این بخش دیدیم، میتوان به متدهایی مانند some، reduceRight، find و every اشاره کرد. در صورت تمایل میتوانید اطلاعات بیشتری را در رابطه با این متدها در وبسایت Mozilla Developer Network کسب کنید.