آیا تا به حال، در جدیدترین کدهای جاوااسکریپت، پیشوند سه نقطه (…) به چشمتان خورده است؟ آیا میدانید کاربرد این سه نقطه چیست و نمایانگر کدام syntax هاست؟
در واقع این سه نقطه، معرف دو ویژگی مهم به نامهای spread و rest در جاوااسکریپت است. قصد داریم در این مقاله، در کنار هم، بیشتر در رابطه با کاربردهای این دو ویژگی بخوانیم و یاد بگیریم. همچنین در نهایت به این موضوع پی ببریم که این دو ویژگی چگونه میتوانند کد شما را مفید و مختصر و کاربردی کنند. تا انتهای این مطلب با ما همراه باشید.
Spread و rest دو ویژگی متفاوت هستند که در EcmaScript 2015 گنجانده شدهاند. همانطور که در ادامه نیز خواهیم دید، میتوان گفت که رفتار این دو ویژگی کمی به یکدیگر شبیه است اما عملکردهایی متفاوت را ارائه می دهند و غالباً از آنها به عنوان مکمل یکدیگر استفاده میشود.
همانطور که گفته شد معرف هر دوی این ویژگیها، سه نقطهای (…) است که با عنوان پیشوند شناخته میشود. به عبارت دیگر، هر زمان که بخواهیم از rest یا spread استفاده کنیم، باید به همراه همین پیشوند سه نقطه باشد. اما اگر معرف هر دوی آنها سه نقطه باشد، چگونه میتوان آنها را از هم متمایز کرد؟
برای اینکه به پاسخ این سوال برسیم، در وهله اول لازم است که عملکرد هر کدام از آنها را بررسی کنیم تا بدانیم که هر یک چه کاری انجام میدهند. پس اکنون برویم سراغ تعریف و کاربردهای spread و rest.
آیا موافق هستید که پیش از توضیحات گسترده، با یک مثال شروع کنیم و توضیحات پس از آن را برایتان ملموستر کنیم؟ پس با دقت به مثال زیر توجه کنید:
const config = { height: 200, width: 400, backgroundColor: 'white', title: 'Just an ordinary window', content: 'something nice...' }; showModalWindow(config); function showModalWindow(config) { const windowConfig = { modal: true, showBackdrop: true, height: config.height, width: config.width, backgroundColor: config.backgroundColor, title: config.title, content: config.content }; // The remaining logic is not relevant for the purpose of this article }
در کد بالا، ابتدا ثابتی به نام config تعریف شده که اطلاعاتی مانند height، width و غیره را در خود ذخیره کرده است. سپس تابعی با نام ShowModalWindow ایجاد شده است که در داخل آن نیز متغیری ویژه با مقادیر جدید تعریف شده است. اما در این تابع، در کنار این مقادیر جدید، برخی از مقادیر متغیر config نیز به چشم میخورد. در حقیقت این دو متغیر ترکیب شدهاند.
نحوه به کارگیری مقادیر config در متغیر جدید به این ترتیب است که ابتدا نام config و سپس یک نقطه (.) و پس از آن صفت مورد نظر نوشته می شود.
همانطور که میبینید، هر آرگومان عیناً در متغیر جدید کپی شده است. این کار برای متغیرهایی که مقادیر کمی دارند، میتواند کارساز باشد. اما تصور کنید که مقادیر زیادی داشته باشیم و بخواهیم در متغیر جدید از آنها بهره بگیریم. آیا باید تمامی مقادیر را یکییکی کپی کنیم و در متغیر جدید بنویسیم؟
مسلم است که این کار چندان منطقی به نظر نمیرسد. اینجاست که عملگر spread وارد عمل میشود و به راحتی این مشکل را حل میکند. spread میتواند در موقعیتهای مختلف ظاهر شود و به خوبی وظیفه خود را انجام دهد. ولی نکتهای که وجود دارد این است که وظیفه آن در تمامی موقعیتها یکسان است.
در ادامه به شرح موقعیتهای استفاده از عملگرد spread میپردازیم و با مثالهایی کاربرد آن را برای شما ملموستر میکنیم.
استفاده از این عملگر بسیار ساده است. تنها کاری که باید انجام دهید این است که قبل از نام متغیر پیشوند سه نقطه (…) بنویسید.
برای درک بیشتر به مثال زیر توجه کنید:
function showModalWindow(config) { const windowConfig = { modal: true, showBackdrop: true, ...config }; // The remaining logic is not relevant for the purpose of this article }
در این کد دیگر خبری از صفتها و مقادیر زیاد و طولانی نیست. برای درج مقادیر متغیر config در داخل متغیری که در تابع showModalWindow تعریف شده، تنها سه نقطهای پیش از نام config نوشته شده است. در حقیقت، این سه نقطه وظیفه درج مقادیر را به عهده میگیرد و به نحو احسن آن را انجام میدهد.
اما در این بین، گاهی ممکن است بخواهید که تنها برخی از مقادیر در تابع یا متغیر جدید اضافه شوند. در این صورت باید متغیری جدید تعریف کنید و توسط تابع، حلقه یا … شرط و شروط خود را بنویسید. در نهایت همین متغیر جدید را فراخوانی کنید تا مقادیر فیلتر شده درج شوند.
به مثال زیر نگاه کنید تا نحوه فیلترینگ را به خوبی درک کنید:
const config = { height: 200, width: 400, backgroundColor: 'white', title: 'Just an ordinary window', unwantedConfig: 'this should not be allowed to pass' }; showModalWindow(config); function showModalWindow(config) { const filteredConfig = // implement some filtering logic here... const windowConfig = { modal: true, showBackdrop: true, ...filteredConfig }; }
هنگامی که عملگر spread برای اولین بار در ES2015 معرفی شد، تنها کاری که انجام میداد ترکیب آرایهها با همدیگر بود. هماکنون نیز عملگر spread در ترکیب آرایهها کاربرد دارد.
ترکیب آرایهها توسط spread بسیار ساده و آسان است. با این حال اجازه دهید با یک مثال پیش برویم:
const n1 = [99, 88, 77]; const n3 = [33, 22, 11]; const n2 = [...n1, 66, 55, 44, ...n3]; console.log(n2); //=> [99, 88, 77, 66, 55, 44, 33, 22, 11]
در کد بالا، سه متغیر با نامهای n1، n2 و n3 تعریف شدهاند. هر کدام از این متغیرها آرایههایی را در بر دارند. اما اگر به متغیر n2 دقت کنید، متوجه میشوید که کمی با سایر متغیرها متفاوت است. در آرایهای که در متغیر n2 ذخیره شده است، علاوهبر مقادیر منحصربهفرد خود، نامهای n1 و n3 به همراه پیشوند سه نقطه نیز به چشم میخورند.
این بدان معنی است که در آرایه n2، ابتدا مقادیر n1 درج میشوند. سپس مقادیر خود n2 و در نهایت مقادیر آرایه n3 اضافه میشود. در نهایت خروجی که به دست میآید این است که سه آرایه با یکدیگر ترکیب و در آرایه n2 ذخیره میشوند.
به مثال دیگری با همین منظور توجه کنید:
const signUpStatuses = ['SIGNUP_BASIC_INFO', 'SIGNUP_PAYMENT_INFO', 'SIGNUP_CONTACT_INFO']; const analysisStatuses = ['PENDING_ANALYSIS', 'REJECTED', 'PENDING_DOCUMENTATION']; const postApprovalStatuses = ['ACTIVE', 'SUSPENDED', 'BLACKLIST']; const statuses = [...signUpStatuses, ...analysisStatuses, ...postApprovalStatuses];
در این مثال نیز همانند مثال قبلی، سه متغیر تعریف شده است. با این تفاوت که مقادیر این آرایهها عبارات و کلمات انگلیسی هستند. حال برای ترکیب این سه آرایه با یکدیگر، متغیری با نام statuses تعریف شده است و مقادیر هر سه آرایه در این متغیر ذخیره شده است.
یکی دیگر از کاربردهای جالب spread این است که با استفاده از آن حتی میتوانید یک آرایه را به صورت یک object درآورید. هرچند که کاربرد این ترفند چندان رایج نیست، اما به هر حال دانستن آن خالی از لطف نیست.
const monthsArr = ['January', 'February', 'March', 'April']; const monthsObj = { ...monthsArr }; console.log(monthsObj); //=> { 0: "January", 1: "February", 2: "March", 3: "April" }
لازم به ذکر است که spread در تابعها و برای فراخونی آنها نیز کاربرد دارد و میتواند آیتمهایی از آرایه را به عنوان آرگومانهای ورودی به تابع فرابخواند.
کاربردهای rest و spread تا حدودی شبیه به هم است. اما یک تفاوت اساسی با یکدیگر دارند. در spread، با یک پیشوند سهنقطه، آرایهها یا objects را از یک متغیر کپی میکردیم و به متغیری دیگر اضافه میکردیم.
اما عملکرد rest کمی با spread متفاوت است. عملکرد rest به این شکل است که آیتمهای آرایه را گروهبندی میکند و سپس به آرایه یا متغیری جدید منتقل میکند و یا بهتر است بگوییم، اضافه میکند.
کمی پیچیده و گجکننده شد. اما نگران نباشید. اجازه دهید با یک مثال آن را بیشتر شرح دهیم:
// Destructuring assignment for arrays const ipOctets = [192, 168, 0, 1]; const [octet1, octet2, octet3, octet4] = ipOctets; console.log('First octet:', octet1); //=> First octet: 192 console.log('Second octet:', octet2); //=> Second octet: 168 console.log('Third octet:', octet3); //=> Third octet: 0 console.log('Fourth octet:', octet4); //=> Fourth octet: 1 // Destructuring assignment for objects const person = { firstName: 'John', lastName: 'Doe', age: 42 }; const { firstName, lastName, age } = person; console.log('First name: ', firstName); //=> John console.log('Last name: ', lastName); //=> Doe console.log('Age: ', age); //=> 42
مثال بالا را با دقت بخوانید. در بخش اول، آرایهای تعریف شده است و آیتمهایی از اعداد در آن قرار گرفتهاند. سپس در سطر دوم، هر یک از آیتمهای آرایه به متغیرهایی مستقل اختصاص داده شدهاند. ممکن است اکنون از خود بپرسید که منظور از این عبارت که آیتمها را به متغیرها اختصاص دادهاند، چیست؟
اگر به خروجیهای این کد نگاه کنید، متوجه میشوید که منظور از سطر دوم در این کد این است که آیتم اول آرایه (یعنی ۱۹۲) در متغیری مستقل به نام octet1 ذخیره میشود. به همین منوال، آیتم دوم (۱۶۸) در متغیر دوم، آیتم سوم در متغیر سوم و الی آخر به ترتیب ذخیره میشوند و در نهایت چاپ میشوند. پس هر آیتمی از آرایه را به متغیرهایی جداگانه اختصاص دادیم.
این کار، علاوهبر آرایهها، در objects نیز کاربرد دارد. بخش دوم مثال بالا نمایانگر همین موضوع است. object هایی تعریف شدهاند و سپس به ترتیب در متغیرهایی مستقل اختصاص داده شدند.
اما حالا تصور کنید که بیشمار از این آیتمهای آرایه داشته باشیم و بخواهیم هرکدام از آنها را در متغیرهایی جداگانه و به ترتیب ذخیره کنیم. چگونه میتوان این کار را انجام داد؟ مسلم است که اختصاص هر یک به صورت تکی کاری زمانبر و غیرعقلانی است.
پس در چنین مواقعی است که به rest نیاز پیدا میکنیم و بهتر است از آن استفاده کنیم. اما نحوه استفاده ازrest به چه شکل است؟
مثالها کار ما را آسانتر کردهاند. پس باز هم برویم سراغ مثالهایی واضح و ملموس:
const user = { name: 'Frederick Charles Krueger', motherName: 'Amanda Krueger', age: 80, address: '1428 Elm Street' }; const { address, ...personalInfo } = user; console.log('Address:', address); //=> 1428 Elm Street console.log(personalInfo); /* => { age: 80, motherName: "Amanda Krueger", name: "Frederick Charles Krueger" } */
در این مثال، در ابتدا object ای تعریف شده است حاوی اطلاعات خاصی مثل نام، آدرس و … است.
حال میخواهیم هر یک از این خصوصیتهای این object را گروهبندی کنیم و در متغیرهایی مستقل جاگذاری کنیم. این کار کاملا به شخص شما بستگی دارد که گروهبندی خصوصیاتتان به چه شکل باشد. در این مثال، address در یک گروه (یا متغیر) و بقیه خصوصیات در گروهی دیگر با نام personalInfo جای گرفتهاند. در نهایت نتیجهای که حاصل میشود این است که مشخصات این دو گروه چاپ میشوند.
در حقیقت میتوان گفت با این کار یک دستهبندی دقیق ایجاد میکنید و به هنگام نیاز به آسانی از آنها در تابع یا متغیرهای دیگر استفاده میکنید.
این کار به خصوص در مواقعی که یک object خصوصیات بسیار زیادی دارد و یا اصلا مشخص نیست که چه تعداد خصوصیت دارد، بسیار کاربردی است. تنها با گذاشتن یک پیشوند سه نقطه، تمامی اطلاعات مورد نیاز در گروهها یا متغیرهایی جداگانه ذخیره و در خروجی چاپ میشوند.
لازم به ذکر است که نحوه استفاده از rest در آرایهها نیز دقیقا مطابق عملکرد آن در objects است. به مثال زیر نگاه کنید تا کاملا متوجه این موضوع نیز شوید:
const ranking = ['ALC', 'QUE', 'JNS', 'BMX', 'TAK', 'SHI', 'BAT', 'CFO', 'ACE', 'CAT']; const [first, second, third, ...others] = ranking; console.log('1st place:', first); //=> "1st place: ALC" console.log('2nd place:', second); //=> "2nd place: QUE" console.log('3rd place:', third); //=> "3rd place: JNS" console.log('Honorable mentions:', others.join(', ')); //=> "Honorable mentions: BMX, TAK, SHI, BAT, CFO, ACE, CAT"
نکته: شاید خود شما هم به این نکته پی برده باشید که موقعیت استفاده از rest تنها در انتهای یک destructuring assignment است. پس اگر کد زیر را در محیط کدنویسی خود کپی کنید، با یک ارور بزرگ مواجه خواهید شد:
const codes = [11, 22, 33, 44, 55]; const [first, ...others, last] = codes;
از rest میتوان در تابعها نیز بهره گرفت. مثلا، میتوانید چندین متغیر را به عنوان اولین پارامترها و ورودیها در تابع تعیین کنید. سپس سایر ورودیها را در یک گروه قرار دهید و آن گروه را به عنوان ورودی تابع بگیرید.
function printRanking(first, second, third, ...others) { console.log('1st place:', first); console.log('2nd place:', second); console.log('3rd place:', third); console.log('Honorable mentions:', others.join(', ')); } printRanking('ALC', 'QUE', 'JNS', 'BMX', 'TAK', 'SHI', 'BAT', 'CFO', 'ACE', 'CAT'); /* => "1st place: ALC" => "2nd place: QUE" => "3rd place: JNS" => "Honorable mentions: BMX, TAK, SHI, BAT, CFO, ACE, CAT" */
حتی در چنین مثالهایی میتوان به طور همزمان از rest و spread استفاده کرد. چرا که این دو میتوانند به خوبی در نقشهای مکمل یکدیگر ظاهر شوند.
امیدواریم از خواندن این مطلب لذت برده باشید و کاملا متوجه نحوه عملکردهای spread و rest شده باشید. نظرات و پیشنهادات خود را با ما در میان بگذارید.
[button class=”github-btn” href=”http://frontcast.ir/become-a-javascript-developer”]پادکست شماره ۵: تبدیل شدن به توسعه دهنده جاوااسکریپت[/button]