ویژگیهای شیء دو نوع هستند.
نوع اول ویژگیهای دادهای هستند. ما از قبل میدانیم چگونه با آنها کار کنیم. تمام ویژگیهایی که تا حالا استفاده میکردیم ویژگیهای دادهای بودند.
نوع دوم ویژگیها چیزی جدید است. این نوع ویژگیهای اکسسر(accessor) است. اساسا آنها تابعهایی هستند که برای گرفتن و تنظیمکردن مقداری اجرا میشوند اما برای یک کد خارجی مانند ویژگیهای معمولی به نظر میرسند.
متدهای getter و setter
ویژگیهای اکسسر توسط متدهای “getter” و “setter” نمایش داده میشوند. در یک شیء لیترال، این متدها با get
و set
مشخص میشوند.
let obj = {
get propName() {
// اجرا میشود obj.propName کد آن برای دریافت ،getter
},
set propName(value) {
// اجرا میشود obj.propName = value کد آن برای تنظیم ،setter
}
};
متد getter زمانی که obj.propName
خوانده میشود کار میکند؛ متد setter زمانی که این ویژگی مقداردهی میشود.
برای مثال، ما یک شیء user
حاوی name
و surname
داریم:
let user = {
name: "John",
surname: "Smith"
};
حالا میخواهیم یک ویژگی fullName
اضافه کنیم که باید "John Smith"
باشد. قطعا، نمیتوانیم اطلاعات موجود را کپیپِیست کنیم پس میتوانیم آن را به عنوان یک اکسسر پیادهسازی کنیم:
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
}
};
alert(user.fullName); // John Smith
از بیرون، یک ویژگی اکسسر مانند ویژگیای معمولی به نظر میرسد. این ایدهی ویژگیهای اکسسر است. ما user.fullName
را به عنوان یک تابع فراخوانی نمیکنیم بلکه آن را به صورت عادی دریافت میکنیم:
از حالا به بعد، fullName
فقط یک getter دارد. اگر ما بخواهیم user.fullName
را مقداردهی کنیم، ارور ایجاد میشود:
let user = {
get fullName() {
return `...`;
}
};
user.fullName = "Test"; // (دارد getter ویژگی فقط) ارور
بیایید با اضافه کردن setter برای user.fullName
این مشکل را برطرف کنیم:
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// همراه با مقدار داده شده اجرا میشود set fullName
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
در نتیجه، ما یک ویژگی «مجازیِ» fullName
داریم. هم قابل خواندن است و هم قابل نوشتن.
توصیفکنندههای اکسسر
توصیفکنندههای ویژگیهای اکسسز نسبت به توصیفکنندههای ویژگیهای دادهای تفاوت دارند.
برای ویژگیهای اکسسر، value
یا writable
وجود ندارد اما به جای آنها تابعهای get
و set
وجود دارد.
یعنی اینکه یک توصیفکننده اکسسز ممکن است اینها را داشته باشد:
- متد
get
– تابعی بدون آرگومان، زمانی که ویژگیای خوانده شود کار میکند، - متد
set
– تابعی با یک آرگومان، زمانی که ویژگی مقداردهی میشود فراخوانی میشود، - ویژگی
enumerable
– مشابه به ویژگیهای دادهای، - ویژگی
configurable
– مشابه به ویژگیهای دادهای،
برای مثال، برای ایجاد اکسسر fullName
با استفاده از defineProperty
، میتوانیم توصیفکنندهای شامل get
و set
قرار دهیم:
let user = {
name: "John",
surname: "Smith"
};
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // John Smith
for(let key in user) alert(key); // name, surname
لطفا در نظر داشته باشید که یک ویژگی یا میتواند اکسسر باشد (دارای متدهای get/set
است) یا یک ویژگی دادهای (یک value
دارد)، نه هر دو.
اگر ما تلاش کنیم که هم get
و هم value
را داخل یک توصیفکننده قرار دهیم، ارور ایجاد میشود:
// ارور: توصیفکنندهی غیر قابل قبول ویژگی
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
متدهای getter/setter هوشمندتر
متدهای getter/setter میتوانند به عنوان دربرگیرنده برای مقدار ویژگیهای «واقعی» استفاده شوند تا کنترل بیشتری بر روی عملیات با آنها داشته باشیم.
برای مثال، اگر ما بخواهیم اسمهای خیلی کوتاهها را برای user
ممنوع کنیم، میتوانیم یک setter name
داشته باشیم و مقدار را درون ویژگی جداگانهی _name
ذخیره کنیم:
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("اسم خیلی کوتاه است، حداقل به 4 کاراکتر نیاز دارد");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // ...اسم خیلی کوتاه است
پس اسم در ویژگی _name
ذخیره شده است و دسترسی گرفتن توسط getter و setter انجام میگیرد.
از لحاظ فنی، کد بیرونی میتواند به صورت مستقیم با استفاده از user._name
به اسم دسترسی پیدا کند. اما یک قرارداد شناخته شده وجود دارد که ویژگیهایی که با یک زیرخط (underscore) "_"
شروع میشوند، داخلی هستند و نباید بیرون از شیء به آنها کاری داشت.
استفاده برای سازگاری
یکی از بهترین کاربردهای اکسسرها این است که آنها به ما اجازه میدهند که در هر زمان یک ویژگی دادهای «معمولی» را از طریق جایگزین کردن آن با یک getter و یک setter کنترل کنیم و رفتار آن را تغییر دهیم.
تصور کنید که ما شروع به پیادهسازی شیءهایی مربوط به کاربران کردیم که شامل ویژگیهای دادهای name
و age
هستند:
function User(name, age) {
this.name = name;
this.age = age;
}
let john = new User("John", 25);
alert( john.age ); // 25
…اما دیر یا زود، همه چیز ممکن است تغییر کند. به جای age
ممکن است تصمیم بگیریم که birthday
را ذخیره کنیم چون دقیقتر و مناسبتر است:
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
let john = new User("John", new Date(1992, 6, 1));
حالا با کد قدیمی که هنوز هم از ویژگی age
استفاده میکند چه کار کنیم؟
میتوانیم چنین جاهایی را در کد پیدا و آنها را درست کنیم اما این کار زمانبر است و اگر آن کد توسط افراد دیگری در حال استفاده باشد ممکن است این کار سخت شود. و همچنین، age
چیز خوبی است که در user
داشته باشیم نه؟
بیایید آن را نگه داریم.
اضافه کردن یک getter برای age
مشکل را حل میکند:
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// از تاریخ کنونی و تاریخ تولد محاسبه میشود age
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // قابل دسترس است birthday
alert( john.age ); // age درست مانند...
حالا کد قدیمی هم کار میکند و ما یک ویژگی اضافی خوب گرفتیم.