تجزیه آرایه ها و اشیاء (Destructuring Assignment)
در این بخش به بررسی موضوعی به نام Destructuring Assignment میپردازیم. با استفاده از Destructuring Assignment میتوان اجزاء تشکیل دهندهی آرایهها یا اشیاء را با روشهای بسیار ساده استخراج کرده و در متغیرها ذخیره کرد.
نکته : ترجمهی دقیق کلمهی Destructuring کمی دشوار است و بیشتر به معنی "تغییر ساختار" به کار میرود. اما در بحث حاضر استفاده از کلمهی "تجزیه" ترجیح داده شده است.
تجزیهی آرایهها (Array Destructuring)
فرض کنید آرایهای به نام colors در اختیار داریم، که نام چند رنگ مختلف را در آن ذخیره کردهایم. حال اگر قصد داشته باشیم مقدار هر یک از عناصر آرایه را در یک متغیر مجزا ذخیره کنیم، بهترین و سادهترین راه ممکن چیست؟ احتمالاً اولین راه حلی که به ذهن میرسد به شکل زیر است.
const colors = ['red' , 'blue' , 'green'];
let color1 = colors[0]; // color1 = 'red'
let color2 = colors[1]; // color2 = 'blue'
let color3 = colors[2]; // color3 = 'green'
البته این روش صحیح است. اما در ES6 امکانات جدیدی به جاوا اسکریپت اضافه شده است که انجام چنین کارهایی را بسیار سادهتر میکند. مجموعهی این امکانات جدید به Destructuring Assignment مشهور هستند. مثلاً دستورات فوق را با استفاده از Destructuring میتوان به شکل زیر سادهسازی کرد.
const colors = ['red' , 'blue' , 'green'];
let [color1 , color2 , color3] = colors;
مشاهده میکنید که سه خط از دستورات قطعه کد قبلی، با یک خط کد جدید جایگزین شده است. در واقع این دو قطعه کد کاملاً معادل یکدیگر هستند و اجرای آنها نتیجهی یکسانی خواهد داشت. همچنین میتوان متغیرهایی که پیشتر تعریف شدهاند را نیز با استفاده از روش فوق مقداردهی کرد. مثلاً در قطعه کد زیر، دو متغیر a و b در یک دستور، توسط یک آرایه مقداردهی میشوند.
let a , b;
[a , b] = [10 , 20]
console.log(a);
← 10
console.log(b);
← 20
نکته : در صورتی که تعداد متغیرهای سمت چپ عملگر انتساب ("=") کمتر از تعداد عناصر آرایهی سمت راست باشند، عناصر اضافی آرایهی سمت راست، به هیچ متغیری اختصاص داده نمیشوند. مثلاً در قطعه کد زیر فقط دو مقدار "red" و "blue" در متغیرهای a و b ذخیره میشوند و از مقدار "green" صرف نظر میشود.
const colors = ['red' , 'blue' , 'green'];
let [a , b] = colors;
البته اینها فقط نمونههای بسیار سادهای از کاربرد Destructuring هستند. در ادامه انواع حالتهای مختلف استفاده از Destructuring را با هم بررسی میکنیم.
جا به جایی مقدار متغیرها
با استفاده از Destructuring به راحتی میتوان مقادیر ذخیره شده در متغیرهای مختلف را با یکدیگر جا به جا کرد. مثلاً در قطعه کد زیر، با استفاده از دستور موجود در خط دوم، مقدار متغیر c به متغیر a، مقدار متغیر a به متغیر b و مقدار متغیر b به متغیر c منتقل میشود.
let [a , b , c] = [10 , 20 , 30];
[a , b , c] = [c , a , b];
console.log(a);
← 30
console.log(b);
← 10
console.log(c);
← 20
مقادیر پیشفرض
در هنگام استفاده از Destructuring، میتوان برای برخی متغیرها مقدار پیشفرض در نظر گرفت. در این صورت اگر برای متغیر مذکور هیچ مقدار متناظری در آرایهی سمت راست وجود نداشته باشد، از مقدار پیشفرض برای آن متغیر استفاده خواهد شد. قطعه کد زیر این موضوع را بهتر روشن میسازد.
let [a=1 , b=2] = [10 , 20];
console.log(a);
← 10
console.log(b);
← 20
[a=1 , b=2] = [10];
console.log(a);
← 10
console.log(b);
← 2
مشاهده میکنید که در دستور انتساب دوم، با توجه به اینکه هیچ مقدار متناظری برای متغیر b وجود ندارد، این متغیر با مقدار پیشفرض ۲ مقداردهی میشود. البته از این روش معمولاً زمانی استفاده میشود که مقداردهی متغیرها توسط خروجی یک تابع انجام شود. یعنی اگر تابعی داشته باشیم که خروجی آن یک آرایه بوده و تعداد عناصر آرایهی خروجی در شرایط مختلف، متفاوت باشد. میتوان از این روش برای مقداردهی متغیرهایی که مقدار متناظری در آرایهی خروجی تابع ندارند، استفاده کرد. قطعه کد زیر نمونهای از استفاده از این روش را نشان میدهد.
function f(){
// بررسی شرایط و مقداردهی متغیرهای لازم برای بررسی شرط
if(condition){
return [1 , 2]
}else{
return [2 , 4 , 7];
}
}
let [a , b , c = 22] = f();
در این قطعه کد، در صورتی که آرایهی خروجی تابع f، شامل ۳ عنصر باشد، مقادیر موجود در آرایه به ترتیب در متغیرهای a و b و c ذخیره میشوند. اما اگر آرایهی خروجی فقط شامل ۲ عنصر باشد، از مقدار پیشفرض ۲۲ برای مقداردهی متغیر c استفاده خواهد شد.
صرف نظر کردن از برخی عناصر آرایه
در مثالهای قبلی تمام انتسابها از عنصر اول آرایه شروع شده و به تعداد متغیرهای موجود در سمت چپ عملگر انتساب ادامه مییافت. اما در برخی مواقع لازم است تا از بعضی عناصر موجود در آرایهی سمت راست صرف نظر کنیم. مثلاً فرض کنید قصد داریم اولین و سومین مقدار موجود در آرایهی colors را در متغیرهای a و b ذخیره کنیم. یعنی باید از مقدار دوم این آرایه صرف نظر کنیم. در چنین شرایطی میتوان به روش زیر عمل کرد.
const colors = ['red' , 'blue' , 'green'];
let [a , , b] = colors;
همانطور که مشاهده میکنید، برای صرف نظر کردن از مقدار دوم (یعنی "blue") باید از عملگر کاما (",") استفاده کنیم. یعنی به ازای هر مقدار از آرایهی سمت راست که قصد صرف نظر کردن از آن را داریم، باید یک کامای اضافی در لیست سمت چپ قرار دهیم.
استفاده از عملگر rest
یکی دیگر از کاربردهای Destructuring، تجزیهی یک آرایه به تعدادی متغیر و یک آرایهی دیگر است. یعنی قصد داریم یک یا چند عنصر ابتدایی آرایه را در یک یا چند متغیر مجزا ذخیره کنیم. و سایر عناصر باقی مانده را در آرایهای دیگر ذخیره کنیم. در چنین شرایطی میتوان از عملگر rest (که قبلاً با آن آشنا شدهایم) استفاده کرد. مثال زیر نحوهی انجام این کار را نشان میدهد.
const colors = ['red' , 'blue' , 'green' , 'yellow' , 'black'];
let [a , b , ...c] = colors;
console.log(a);
← "red"
console.log(b);
← "blue"
console.log(c);
← ["green" , "yellow" , "black"];
مشاهده میکنید که سه عنصر انتهای آرایهی colors، به صورت یک آرایه در متغیر c ذخیره شدهاند. توجه کنید که در صورت استفاده از عملگر rest، حتماً باید این عملگر را در کنار آخرین متغیر از لیست سمت چپ به کار ببرید. در غیر این صورت یک خطای دستوری (Syntax Error) تولید شده و برنامه متوقف میشود.
تجزیهی اشیاء (Object Destructuring)
تقریباً تمام کارهایی که برای تجزیهی آرایهها قابل انجام هستند، برای تجزیهی اشیاء نیز قابل انجام میباشند. یعنی میتوان با دستوراتی بسیار ساده، خاصیتها و متدهای یک شئ را در متغیرهای دیگری ذخیره کرد. به مثال زیر توجه کنید.
const obj = {p: 42, q: true};
let {p , q} = obj;
console.log(p);
← 42
console.log(q);
← true
مشاهده میکنید که مقدار خاصیتهای p و q از شئ obj، با استفاده از یک دستور ساده در متغیرهای p و q ذخیره شدهاند. دقت کنید که متغیرهای p و q کاملاً مجزا از خاصیتهای p و q از شئ obj هستند. اما لازم است که حتماً نام این متغیرها با نام خاصیتهای شئ سمت راست یکسان باشد. زیرا در تجزیهی اشیاء بر خلاف تجزیهی آرایهها، ترتیب اهمیتی ندارد و از نام خاصیتها و متدها، برای یافتن متغیر متناظر در سمت چپ استفاده میشود. در نتیجه اگر در مثال فوق p و q را در دستور دوم جا به جا کنیم، باز هم همان نتیجهی قبلی را دریافت خواهیم کرد.
const obj = {p: 42, q: true};
let {q , p} = obj;
console.log(p);
← 42
console.log(q);
← true
اما در برخی مواقع لازم است نام متغیرهای جدید، با نام خاصیتها و متدهای موجود در شئ سمت راست متفاوت باشند. در چنین شرایطی میتوان نام جدید متغیرها را به شکل زیر تعیین کرد.
const obj = {p: 42, q: true};
let {p : a , q : b} = obj;
console.log(a);
← 42
console.log(b);
← true
مشاهده میکنید که مقدار خاصیت p در متغیری با نام a و مقدار خاصیت q در متغیری با نام b ذخیره شدهاند.
نکته : در صورتی که تجزیهی اشیاء بدون تعریف متغیرهای جدید انجام شود (از کلمات کلیدی let، const و var استفاده نشود)، کل دستور مربوط به تجزیه باید داخل پرانتز قرار گیرد (مانند مثال زیر). در غیر این صورت یک خطای دستوری تولید شده و برنامه متوقف میشود.
let a , b;
({a , b} = {a: 3 , b: 5});
مقادیر پیشفرض
در تجزیهی اشیاء نیز میتوان از مقادیر پیشفرض برای متغیرها استفاده کرد. در این صورت برای متغیرهایی که هیچ خاصیت یا متد متناظری در شئ سمت راست ندارند، از مقدار پیشفرض استفاده خواهد شد. مثال زیر نحوهی استفاده از مقادیر پیشفرض در تجزیهی اشیاء را نشان میدهد.
let {a = 10, b = 5} = {a: 3};
console.log(a);
← 3
console.log(b);
← 5
همچنین ممکن است هنگام استفاده از مقادیر پیشفرض، نیاز به تغییر نام خاصیتها و متدها نیز داشته باشیم. در چنین شرایطی میتوان مانند مثال زیر این عمل را انجام داد.
let {a: aa = 10, b: bb = 5} = {a: 3};
console.log(aa);
← 3
console.log(bb);
← 5
Destructuring در پارامترهای توابع
با استفاده از Destructuring میتوان اشیائی که به عنوان آرگومان ورودی به توابع ارسال میشوند را به شکلی تجزیه کرد که در بدنهی تابع، به شکل سادهتری بتوان به اجزاء شئ مورد نظر دسترسی پیدا کرد. به مثال زیر توجه کنید.
let user = {
id: 42,
displayName: 'jdoe',
fullName: {
firstName: 'John',
lastName: 'Doe'
}
};
function userId({id}) {
return id;
}
function whois({displayName, fullName: {firstName: name}}) {
return `${displayName} is ${name}`;
}
console.log(userId(user));
← 42
console.log(whois(user));
← "jdoe is John"
در مثال فوق، در تابع userId فقط خاصیت id از شئ ارسال شده استخراج میشود و در بدنهی تابع با همان نام id مورد استفاده قرار میگیرد. در تابع whois نیز خاصیت displayName از شئ user و خاصیت firstName از شئ fullName (که خود متعلق به شئ user است) استخراج شده و در بدنهی تابع مورد استفاده قرار میگیرند. توجه کنید که نام خاصیت firstName در هنگام تجزیه به name تغییر کرده است. پس در بدنهی تابع فقط با نام name میتوان به آن دسترسی داشت.
همانطور که مشاهده میکنید با استفاده از Destructuring Assignment تعداد خطوط کدها به مقدار قابل توجهی کاهش یافته و خوانایی برنامه نیز تا حدودی افزایش مییابد. البته هنوز نکات دیگری در رابطه با Destructuring وجود دارد که در این بخش به دلیل کاربرد کمتر، از ذکر آنها خودداری میکنیم. اما در صورت تمایل میتوانید با مراجعه به این آدرس، اطلاعات بیشتری در این زمینه کسب کنید.