دو ساختار داده پر استفاده در جاوااسکریپت Object
و Array
هستند.
- شیءها به ما این امکان را میدهند تا چیزی بسازیم که المانهای داده را به واسطه کلید ذخیره کند.
- آرایهها به ما امکان جمعآوری المانهای داده را در لیستی مرتب میدهند.
اگرچه، زمانی که ما آنها را به تابع میدهیم، ممکن است که نیازی به کل یک شیء/آرایه نباشد. شاید تنها قطعههای تکی نیاز باشد.
مقداردهیِ تجزیهکنندهی ساختار (Destructuring assignment) یک سینتکس خاص است که به ما امکان میدهد تا آرایهها یا شیءها را درون چند متغیر «پخش کنیم» چون بعضی اوقات این موضوع کار را راحتتر میکند.
تخریب ساختار همچنین با تابعهای پیچیده که تعداد زیادی پارامتر، مقدارهای پیشفرض و… دارند هم به خوبی کار میکند. به زودی آن را خواهیم دید.
تجزیه ساختار آرایه
کد پایین یک مثال از چگونگی تبدیل یک آرایه به چند متغیر است:
// ما یک آرایه شامل نام و نام خانوادگی داریم
let arr = ["John", "Smith"]
// مقداردهی تجزیهکنندهی ساختار
// را قرار میدهد firstName = arr[0]
// surname = arr[1] و
let [firstName, surname] = arr;
alert(firstName); // John
alert(surname); // Smith
حالا ما میتوانیم به جای اعداد آرایه با متغیرها کار کنیم.
زمانی که با split
یا متدهای دیگری که آرایه برمیگردانند عالی بنظر میرسد:
let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname); // Smith
همانطور که میبینید، سینتکس ساده است. البته چند چیز ویژه در جزییات خود دارد. بیایید برای فهمیدن بهتر آن، مثالهای بیشتری ببینیم.
این سینتکس «مقداردهی تجزیهکنندهی ساختار» نامیده میشود چون با کپی کردن المانها در چند متغیر «ساختار را تغییر میدهد». اما خود آرایه تغییر نمیکند.
فقط یک راه کوتاهتر برای نوشتن است:
// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
المانهایی که که نمیخواهیم را میتوان با یک کامای اضافه دور انداخت:
// به المان دوم نیاز نداریم
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
در کد بالا، از المان دوم آرایه گذشتیم، المان سوم به title
تخصیص داده شد و بقیه المانهای آرایه هم نادیده گرفته شدند (به دلیل اینکه متغیری برای ذخیره آنها وجود ندارد).
…در واقع، ما میتوانیم آن را با هر حلقهپذیری استفاده کنیم، نه فقط آرایهها:
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
این کار میکند چون از درون، یک مقداردهی تجزیهکنندهی ساختار با حلقه زدن در مقدار سمت راست کار میکند. برای فراخوانی for..of
در مقدار سمت راست =
و تخصیص دادن مقدارها، به نوعی خوش سینتکس است.
ما میتوانیم از «قابل مقداردهیها» در سمت چپ استفاده کنیم.
برای مثال، یک ویژگی شیء:
let user = {};
[user.name, user.surname] = "John Smith".split(' ');
alert(user.name); // John
alert(user.surname); // Smith
در فصل قبل ما متد Object.entries(obj) را دیدیم.
میتوانیم آن را با تجزیهکنندهی ساختار برای حلقه زدن در کلیدها و مقدارهای یک شیء استفاده کنیم:
let user = {
name: "John",
age: 30
};
// حلقه زدن در کلیدها و مقدارها
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
کد مشابه برای Map
سادهتر است چون حلقهپذیر است:
let user = new Map();
user.set("name", "John");
user.set("age", "30");
// حلقه میسازد، برای تجزیهکنندهی ساختار بسیار مناسب است [key, value] با جفتهای Map
for (let [key, value] of user) {
alert(`${key}:${value}`); // age:30 سپس name:John
}
یک ترفند معروف برای مبادله مقدارهای دو متغیر با استفاده از مقداردهی تجزیهکنندهی ساختار وجود دارد:
let guest = "Jane";
let admin = "Pete";
// guest=Pete، admin=Jane بیایید مقدارها را مبادله کنیم: کاری کنیم که
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane (!با موفقیت مبادله شد)
اینجا ما یک آرایه موقتی از دو آرایه میسازیم و بلافاصله ساختار آن را نسبت به ترتیب مبادله تجزیه میکنیم.
میتوانیم از این راه بیشتر از دو متغیر را مبادله کنیم.
رِست ‘…’
معمولا، اگر آرایه طولانیتر از لیست سمت چپ باشد، المانهای «اضافی» از قلم میافتند.
برای مثال، اینجا فقط دو المان دریافت میشود و بقیه نادیده گرفته میشوند:
let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// المانهای بعدی جایی ذخیره نمیشوند
اگر بخواهیم تمام المانهای بعدی را دریافت کنیم – میتوانیم یک پارامتر دیگر اضافه کنیم که با استفاده از "..."
«بقیه المانها» را دریافت کند:
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// آرایهای از المانها است که از المان سوم شروع میشود rest
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
مقدار rest
آرایهای از المانهای باقی مانده است.
میتوانیم از هر اسم دیگری به جای rest
برای متغیر استفاده کنیم، فقط مطمئن شوید که قبل از آن سه نقطه وجود دارد و در انتهای مقداردهی تجزیهکنندهی ساختار قرار میگیرد.
let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// titles = ["Consul", "of the Roman Republic"] حالا داریم
مقدارهای پیشفرض
اگر آرایه از لیست متغیرهای سمت چپ کوتاهتر باشد، هیچ اروری ایجاد نمیشود. مقدارهای ناموجود undefined در نظر گرفته میشوند:
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
ما یک مقدار «پیشفرض» بخواهیم که جایگزین مقدار ناموجود شود، میتوانیم با استفاده از =
آن را فراهم کنیم:
// مقدارهای پیشفرض
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (از آرایه)
alert(surname); // Anonymous (مقدار پیشفرض استفاده شد)
مقدارهای پیشفرض میتوانند عبارات پیچیدهتر یا حتی فراخوانی تابع باشند. آنها فقط زمانی که مقدار وجود نداشته باشد ارزیابی میشوند.
برای مثال، اینجا ما از تابع prompt
برای دو مقدار پیشفرض استفاده کردیم:
// اجرا میشود prompt تابع suname فقط برای
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];
alert(name); // Julius (از آرایه)
alert(surname); // دریافت کند prompt هر چیزی که
لطفا در نظر داشته باشید: prompt
فقط برای مقدار ناموجود (surname
) اجرا میشود.
تجزیه ساختار شیء
مقداردهی تجزیهکنندهی ساختار با شیءها هم کار میکند.
سینتکس ساده آن اینگونه است:
let {var1, var2} = {var1:…, var2:…}
ما باید یک شیء موجود که میخواهیم آن را در چند متغیر پخش کنیم در سمت راست داشته باشیم. سمت چپ شامل یک «الگوی» شیء مانند برای ویژگیهای متناظر میشود. در سادهترین حالت، یک لیست از اسمهای متغیر در {...}
است.
برای مثال:
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
ویژگیهای options.title
، options.width
و options.height
به متغیرهای متناظر تخصیص داده شدهاند.
ترتیب مهم نیست. اینگونه هم کار میکند:
// تغییر دادیم let {...} ترتیب را در
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
الگوی سمت راست ممکن است پیچیدهتر باشد و رابطه بین ویژگیها و متغیرها را تعیین کند.
اگر ما بخواهیم که یک ویژگی را به یک متغیر با نامی دیگر تخصیص بدهیم، برای مثل، کاری کنیم که options.width
در متغیری به نام w
ذخیره شود، میتوانیم اسم متغیر یا با استفاده از دو نقطه تنظیم کنیم:
let options = {
title: "Menu",
width: 100,
height: 200
};
// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); // 100
alert(h); // 200
دو نقطه نشان میدهد که «چه چیزی : کجا ذخیره میشود». در مثال بالا ویژگی width
درون w
، ویژگی height
درون h
ذخیره و title
به اسمی مشابه با خودش تخصیص داده میشود.
برای ویژگیهایی که ممکن است موجود نباشند ما میتوانیم با استفاده از "="
مقدار پیشفرض قرار دهیم، مثلا اینگونه:
let options = {
title: "Menu"
};
let {width = 100, height = 200, title} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
درست مانند آرایهها یا پارامترهای تابع، مقدارهای پیشفرض میتوانند هر عبارتی یا حتی فراخوانی تابع باشند. اگر مقدار موجود نباشد، آنها ارزیابی میشوند.
در کد زیر prompt
برای width
درخواست میکند اما برای title
نه:
let options = {
title: "Menu"
};
let {width = prompt("width?"), title = prompt("title?")} = options;
alert(title); // Menu
alert(width); // (باشد prompt هر چیزی که نتیجه)
همچنین ما میتوانیم دو نقطه و برابر قرار دادن را با هم ترکیب کنیم:
let options = {
title: "Menu"
};
let {width: w = 100, height: h = 200, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
اگر ما یک شیء پیچیده با تعداد زیادی ویژگی داشته باشیم، میتوانیم فقط چیزی که نیاز داریم را استخراج کنیم:
let options = {
title: "Menu",
width: 100,
height: 200
};
// را به عنوان متغیر استخراج کنید title فقط
let { title } = options;
alert(title); // Menu
الگوی رست “…”
اگر شیء بیشتر از تعدادی که ما متغیر داریم ویژگی داشته باشد چه اتفاقی میافتد؟ آیا میتوانیم بعضی از آنها را دریافت کنیم و «بقیه» را جایی دیگر ذخیره کنیم؟
میتوانیم از الگوری رست استفاده کنیم، درست همانطور که با آرایهها این کار را کردیم. این کار توسط بعضی از مرورگرهای قدیمی پشتیبانی نمیشود (IE، از Babel برای رفع این مشکل استفاده کنید) اما در مرورگرهای جدید کار میکند.
اینگونه بنظر میرسد:
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = title ویژگیای به اسم
// rest = شیءای شامل بقیه ویژگیها
let {title, ...rest} = options;
// title="Menu" ،rest={height: 200, width: 100} حالا داریم
alert(rest.height); // 200
alert(rest.width); // 100
let
وجود نداشته باشد گرفتار میشویمدر مثالهای بالا متغیرها دقیقا درون مقداردهی تعریف شدند: let {…} = {…}
. قطعا ما میتوانستیم بدون let
، از متغیرهای موجود هم استفاده کنیم. اما یک مشکل وجود دارد.
این کار نمیکند:
let title, width, height;
// در این خط ارور میگیریم
{title, width, height} = {title: "Menu", width: 200, height: 100};
مشکل اینجاست که جاوااسکریپت در کد اصلی (نه درون یک عبارت دیگر) با {...}
به عنوان یک بلوک کد رفتار میکند. چنین بلوکهای کدی میتوانند برای گروهبندی دستورات استفاده شوند، مثلا اینگونه:
{
// یک بلوک کد
let message = "سلام";
// ...
alert( message );
}
پس اینجا جاوااسکریپت فرض میکند که ما یک بلوک کد داریم و به همین دلیل است که ارور ایجاد میشود. به جای آن ما تجزیه ساختار میخواهیم.
برای اینکه به جاوااسکریپت نشان دهیم که این یک بلوک کد نیست، میتوانیم عبارت را درون پرانتز (...)
قرار دهیم:
let title, width, height;
// الان مناسب است
({title, width, height} = {title: "Menu", width: 200, height: 100});
alert( title ); // Menu
تجزیه ساختار تودرتو
اگر یک شیء یا آرایه، شیء و آرایههای تودرتو دیگری را شامل شود، ما میتوانیم از الگوری پیچیدهتری در سمت چپ برای استخراج قسمتهای عمیقتر استفاده کنیم.
در کد زیر options
یک شیء دیگری درون ویژگی size
و یک آرایه درون ویژگی items
دارد. الگوی سمت چپ مقداردهی ساختار یکسانی برای استخراج مقدار از آنها را دارد:
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// مقداردهی تجزیهکنندهی ساختار برای واضح بودن در چند خط قرار گرفته است
let {
size: { // را اینجا قرار دهید size
width,
height
},
items: [item1, item2], // را اینجا تخصیص دهید items
title = "Menu" // در شیء وجود ندارد (مقدار پیشفرض استفاده میشود)
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
تمام ویژگیهای شیء options
به جز extra
که در سمت چپ وجود ندارد، به متغیرهای متناظر خود تخصیص داده شدهاند:
سرانجام، ما width
، height
، item1
، item2
و متغیر title
را از مقدار پیشفرض داریم.
در نظر داشته باشید که هیچ متغیری برای size
و items
وجود ندارد چون به جای آنها، ما محتوایشان را میخواهیم.
پارامترهای هوشمند تابع
بعضی اوقات پیش میآید که یک تابع پارامترهای زیادی داشته باشد که اکثر آنها الزامی نیستند. خصوصا برای رابط کاربری این اتفاق میافتد. یک تابع را تصور کنید که یک منو ایجاد میکند. این منو ممکن است دارای طول(width)، ارتفاع(height)، عنوان(title)، لیستی از کالاها و غیره باشد.
این یک راه برای نوشتن چنین تابعی است:
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
// ...
}
در واقعیت، مشکل این است که چگونه ترتیب آرگومانها را به یاد بسپاریم. معمولا محیطهای کدنویسی (IDE) سعی میکنند به ما کمک کنند، مخصوصا اگر کد، مستند خوبی داشته باشد اما باز هم… مشکل دیگری که وجود دارد این است که چگونه یک تابع را زمانی که اکثر پارامترها به صورت پیشفرض مشکلی ندارند فراخوانی کنیم.
مثلا اینگونه؟
// میگذاریم undefined جایی که مقدارهای پیشفرض مشکلی ندارند
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
این قشنگ نیست. و زمانی که با پارامترهای بیشتری سر و کار داشته باشیم خوانایی کمتری دارد.
تجزیه ساختار به نجات ما میآید!
ما میتوانیم پارامترها را به عنوان شیء رد کنیم و تابع بلافاصله با تجزیه ساختار، آنها را در متغیرها پخش میکند:
// ما شیء را به تابع میدهیم
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// و تابع بلافاصله آن را در متغیرها پراکنده میکند...
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// ،از شیء گرفته شدهاند title ،items
// ،مقدارهای پیشفرض استفاده شدند width ،height
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
همچنین میتوانیم از تجزیه ساختار پیچیدهتری همراه با شیءهای تودرتو و طراحی دو نقطهای استفاده کنیم:
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // میرود w درون width
height: h = 200, // میرود h درون height
items: [item1, item2] // میرود item2 و دومی درون item1 درون items اولین المان
}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
سینتکس کامل برای یک مقداردهی تجزیهکنندهی ساختار یکسان است:
function({
incomingProperty: varName = defaultValue
...
})
سپس به صورت پیشفرض، برای یک شیء شامل پارامترها، یک متغیر varName
(اسم متغیر) برای ویژگی incomingProperty
(ویژگی ورودی)، همراه با defaultValue
وجود خواهد داشت.
لطفا در نظر داشته باشید که چنین تجزیه ساختاری فرض میکند که showMenu()
یک آرگومان دارد. اگر ما تمام مقدارهای پیشفرض را بخواهیم، پس باید یک شیء خالی مشخص کنیم:
showMenu({}); // خب، تمام مقدارها پیشفرض هستند
showMenu(); // این ارور خواهد داد
ما میتوانیم با قرار دادن {}
به عنوان مقدار پیشفرض برای تمام شیء شامل پارامترها این مشکل را رفع کنیم:
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
alert( `${title} ${width} ${height}` );
}
showMenu(); // Menu 100 200
در کد بالا، تمام شیء آرگومانها به صورت پیشفرض {}
است پس همیشه چیزی برای تجزیه ساختار وجود دارد.
خلاصه
-
مقداردهی تجزیهکنندهی ساختار به ما اجازه میدهد تا بلافاصله یک شیء یا آرایه را روی بسیاری از متغیرها ترسیم کنیم.
-
سینتکس کامل شیء:
let {prop : varName = default, ...rest} = object
این به این معنی است که ویژگی
prop
باید درون متغیرvarName
برود و اگر چنین ویژگیای وجود نداشت، سپس مقدارdefault
باید استفاده شود.ویژگیهای شیء که نقشی برای آنها وجود ندارد درون شیء
rest
کپی میشوند. -
سینتکس کامل آرایه:
let [item1 = default, item2, ...rest] = array
المان اول درون
item1
میرود؛ المان دوم درونitem2
میشود، تمام المانهای باقی مانده آرایهrest
را تشکیل میدهند. -
استخراج داده از آرایه/شیءهای تودرتو هم ممکن است، برای اینکار سمت چپ باید ساختار یکسانی با سمت راست داشته باشد.