همانطور که میدانیم، شیءها میتوانند ویژگیهایی را ذخیره کنند.
تا حالا، یک ویژگی برای ما فقط جفتی ساده از «کلید-مقدار» بود. اما یک ویژگی شیء در واقع چیزی منعطفتر و قدرتمندتر است.
در این فصل ما درباره گزینههای اضافی پیکربندی مطالعه خواهیم کرد و در فصل بعد خواهیم دید که چگونه به طور پنهانی آنها را به تابعهای گیرنده/تنظیمکننده (getter/setter functions) تبدیل کنیم.
پرچمهای ویژگی (Property flags)
ویژگیهای شیء، در کنار value
دارای سه صفت (attribute) هم هستند (اصطلاحا «پرچم» یا flag هم میگویند):
writable
– اگرtrue
باشد، مقدار میتواند تغییر کند، در غیر این صورت مقدار فقط برای خواندن است.enumerable
– اگرtrue
باشد، ویژگی در حلقهها لیست میشود، در غیر این صورت لیست نمیشود.configurable
– اگرtrue
باشد، ویژگی میتواند حذف شود و این صفتها میتوانند تغییر کنند، در غیر این صورت هیچکدام مقدور نیست.
چون اینها معمولا نمایان نمیشوند، هنوز آنها را ندیدیم. زمانی که «از راه عادی» یک ویژگی ایجاد میکنیم، تمام آنها true
هستند. اما میتوانیم هر زمان تغییرشان دهیم.
اول بیایید ببینیم چگونه این پرچمها بدست آوریم.
متد Object.getOwnPropertyDescriptor به ما اجازه میدهد تا اطلاعات کاملی درباره یک ویژگی بدست آوریم.
سینتکس آن:
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
- شیءای که از آن اطلاعات دریافت میکنیم.
propertyName
- اسم ویژگی.
مقدار برگردانده شده، یک شیء به اصطلاح «توصیفکننده ویژگی(property descriptor)» است: این شیء شامل مقدار و تمام پرچمها میشود.
برای مثال:
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* :توصیفکننده ویژگی
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
برای تغییر پرچمها، میتوانیم از Object.defineProperty استفاده کنیم.
سینتکس آن:
Object.defineProperty(obj, propertyName, descriptor)
obj
,propertyName
- شیء و ویژگیای که توصیفکننده روی آن اعمال میشود.
descriptor
- شیء توصیفکننده ویژگی برای اعمال کردن.
اگر ویژگی وجود داشته باشد، defineProperty
پرچمهای آن را بروزرسانی میکند. در غیر این صورت، این متد ویژگی را همراه با مقدار و پرچمهای داده شده ایجاد میکند؛ در این صورت، اگر پرچمی قرار داده نشود، false
فرض میشود.
برای مثال، اینجا ویژگی name
با تمام پرچمهای falsy ساخته شده است:
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
این خروجی را با user.name
بالا «که به صورت عادی ساخته شده» مقایسه کنید: حالا تمام پرچمها falsy هستند. اگر این چیزی که ما میخواهیم نیست، پس بهتر است درون descriptor
آنها را برابر با true
قرار دهیم.
حالا بیایید با استفاده از مثال تاثیر پرچمها را ببینیم.
غیر قابل نوشتن
بیایید با تغییر دادن پرچم writable
کاری کنیم که user.name
غیر قابل نوشتن شود:
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false
});
user.name = "Pete"; // مقدار داد 'name' ارور: نمیتوان به ویژگی فقطخواندنی
حالا هیچکس نمیتواند اسم کاربر ما را تغییر دهد، مگر اینکه defineProperty
خودش را برای باطل کردن توصیفکنندهی ما اعمال کند.
در حالت غیر strict، زمانی که بر روی ویژگیهای غیرقابل نوشتن مینویسیم، هیچ اروری رخ نمیدهد. اما همچنان این کار انجام نمیشود. در حالت غیر strict، کارهای نقصکنندهی پرچم بی سر و صدا نادیده گرفته میشوند.
اینجا مثالی مشابه داریم اما ویژگی از اول ایجاد شده است:
let user = { };
Object.defineProperty(user, "name", {
value: "John",
// است true برای ویژگیهای جدید ما باید به طور واضح لیست کنیم که چه چیزی
enumerable: true,
configurable: true
});
alert(user.name); // John
user.name = "Pete"; // Error
غیر قابل شمارش
حالا بیایید یک toString
سفارشی به user
اضافه کنیم.
طبیعتا، یک toString
درونساخت برای شیءها غیر قابل شمارش است و در for..in
ظاهر نمیشود. اما اگر ما toString
خودمان را اضافه کنیم، سپس به طور پیشفرض درون for..in
نمایش داده میشود، مثلا اینگونه:
let user = {
name: "John",
toString() {
return this.name;
}
};
// :به طور پیشفرض هر دو ویژگی ما لیست میشوند
for (let key in user) alert(key); // name, toString
اگر ما نخواهیم که اینطور باشد، میتوانیم enumerable:false
را تنظیم کنیم. سپس این ویژگی درون حلقه for..in
ظاهر نمیشود، درست مانند متد درونساخت آن:
let user = {
name: "John",
toString() {
return this.name;
}
};
Object.defineProperty(user, "toString", {
enumerable: false
});
// :ما ظاهر نمیشود toString حالا
for (let key in user) alert(key); // name
ویژگیهای غیر قابل شمارش از Object.keys
هم حذف میشوند:
alert(Object.keys(user)); // name
غیر قابل تنظیم
پرچم غیر قابل تنظیم (configurable:false
) بعضی اوقات برای شیءها و ویژگیهای درونساخت ارائه میشود.
یک ویژگی غیر قابل تنظیم نمیتواند حذف شود و صفتهای آن نمیتوانند تغییر کنند.
برای مثال، Math.PI
غیر قابل نوشتن، غیر قابل شمارش و غیر قابل تنظیم است:
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": 3.141592653589793,
"writable": false,
"enumerable": false,
"configurable": false
}
*/
پس یک برنامهنویس نمیتواند مقدار Math.PI
را تغییر دهد یا آن را دوباره بنویسد.
Math.PI = 3; // writable: false ارور، چون
// هم کار نمیکند Math.PI حذف
همچنین ما نمیتوانیم Math.PI
را تغییر دهیم تا دوباره writable
(قابل نوشتن) باشد:
// configurable: false ارور، چون
Object.defineProperty(Math, "PI", { writable: true });
هیچ کاری نمیتوانیم با Math.PI
انجام دهیم.
غیر قابل تنظیم کردن یک ویژگی راهی یکطرفه است. ما نمیتوانیم آن را با defineProperty
دوباره تغییر دهیم.
لطفا در نظر داشته باشید: configurable: false
از تغییرات پرچمهای ویژگی و حذف آن جلوگیری میکند در حالی که تغییر مقدار آن مجاز است.
اینجا user.name
غیر قابل تنظیم است اما همچنان میتوانیم آن را تغییر دهیم (چون قابل نوشتن است):
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
configurable: false
});
user.name = "Pete"; // به درستی کار میکند
delete user.name; // ارور
و اینجا ما کاری میکنیم که user.name
برای همیشه «مهر و موم شده» بماند، درست مانند Math.PI
درونساخت:
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false,
configurable: false
});
// یا پرچمهای آن را تغییر دهیم user.name نمیتوانیم
// :هیچ کدام اینها کار نخواهند کرد
user.name = "Pete";
delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
یک استثنای کوچک درباره تغییر پرچمها وجود دارد.
ما میتوانیم برای یک ویژگی غیر قابل تنظیم writable: true
را به false
تغییر دهیم و به این ترتیب از تغییر مقدار آن جلوگیری کنیم (تا لایهای دیگر از حفاظت را اضافه کنیم). اما برعکس آن ممکن نیست.
متد Object.defineProperties
یک متد Object.defineProperties(obj, descriptors) وجود دارد که امکان توصیف چند ویژگی با هم را ایجاد میکند.
سینتکس آن:
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
برای مثال:
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
پس ما میتوانیم چند ویژگی را یکباره تنظیم کنیم.
متد Object.getOwnPropertyDescriptors
برای گرفتن تمام توصیفکنندههای ویژگی با هم، میتوانیم از متد Object.getOwnPropertyDescriptors(obj) استفاده کنیم.
این متد همراه با Object.defineProperties
میتواند به عنوان راهی «همراه با پرچمها» برای کپی کردن یک شیء استفاده شود:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
طبیعتا زمانی که ما یک شیء را کپی میکنیم، از عملگر مقداردهی برای کپی کردن ویژگیها استفاده میکنیم، مانند اینجا:
for (let key in user) {
clone[key] = user[key]
}
…اما این روش پرچمها را کپی نمیکند. پس اگر ما کپیبرداری «بهتری» بخواهیم Object.defineProperties
ترجیح داده میشود.
تفاوتی دیگر این است که for..in
ویژگیهای سمبلی (symbolic) و ویژگیهای غیر قابل شمارش را نادیده میگیرد، اما Object.getOwnPropertyDescriptors
تمام توصیفکنندههای ویژگیها را برمیگرداند که شامل ویژگیهای سمبلی و غیر قابل شمارش هم میشود.
مهر و موم کردن شیء به طور کلی
توصیفکنندههای ویژگیها با ویژگیهای جداگانه کار میکنند.
متدهایی هم هستند که دسترسی به کل شیء را محدود میکنند:
- Object.preventExtensions(obj)
- اضافه کردن ویژگی جدید به شیء را ممنوع میکند.
- Object.seal(obj)
- اضافه/حذف کردن ویژگی را ممنوع میکند.
configurable: false
را برای تمام ویژگیهای موجود تنظیم میکند. - Object.freeze(obj)
- اضافه/حذف/تغییر دادن ویژگیها را ممنوع میکند.
configurable: false, writable: false
را برای تمام ویژگیهای موجود تنظیم میکند.
همچنین آزمایشهایی هم برای آنها وجود دارد:
- Object.isExtensible(obj)
- اگر اضافه کردن ویژگی ممنوع باشد
false
برمیگرداند، در غیر این صورتtrue
. - Object.isSealed(obj)
- اگر اضافه/حذف کردن ویژگی ممنوع باشد و تمام ویژگیهای موجود
configurable: false
را داشته باشندtrue
برمیگرداند. - Object.isFrozen(obj)
- اگر اضافه/حذف/تغییر دادن ویژگیها ممنوع باشد و تمام ویژگیها
configurable: false, writable: false
را داشته باشندtrue
برمیگرداند.
این متدها به ندرت در عمل استفاده میشوند.