Kétféle objektumtulajdonság létezik.
Az első fajta az adattulajdonságok. Már tudjuk, hogyan kell velük dolgozni. Minden tulajdonság, amit eddig használtunk, adattulajdonság volt.
A második fajta tulajdonság valami új. Ez a hozzáférési tulajdonságok. Ezek lényegében olyan függvények, amelyek egy érték megszerzésekor és beállításakor végrehajtódnak, de egy külső kód számára úgy néznek ki, mint a hagyományos tulajdonságok.
Getterek és setterek
Az accessor tulajdonságokat “getter” és “setter” metódusok képviselik. Egy objektum literálban ezeket get
és set
jelöli:
let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};
A getter akkor működik, amikor obj.propName
beolvassuk, a setter – amikor hozzárendeljük.
Egy user
objektumunk van például name
és surname
tulajdonságokkal:
let user = { name: "John", surname: "Smith"};
Most szeretnénk hozzáadni egy fullName
tulajdonságot, aminek "John Smith"
-nek kell lennie. Természetesen nem akarjuk a meglévő információt copy-paste módon beilleszteni, ezért megvalósíthatjuk accessorként:
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith
Kívülről nézve egy accessor tulajdonság úgy néz ki, mint egy hagyományos. Ez az accessor tulajdonságok lényege. A user.fullName
-ot nem függvényként hívjuk meg, hanem normálisan olvassuk: a getter a színfalak mögött fut.
A fullName
-nek mostantól csak egy gettere van. Ha megpróbáljuk hozzárendelni a user.fullName=
-t, hiba fog jelentkezni:
let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)
Hozzuk helyre úgy, hogy a user.fullName
-hoz hozzáadunk egy settert:
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { = value.split(" "); }};// set fullName is executed with the given value.user.fullName = "Alice Cooper";alert(user.name); // Alicealert(user.surname); // Cooper
Az eredmény egy “virtuális” tulajdonság fullName
lesz. Ez olvasható és írható.
Accessor leírók
A accessor tulajdonságok leírói különböznek az adat tulajdonságoktól.
A accessor tulajdonságok esetében nincs value
vagy writable
, helyette get
és set
függvények vannak.
Ez azt jelenti, hogy egy accessor leíró lehet:
-
get
– egy argumentumok nélküli függvény, amely a tulajdonság olvasásakor működik, -
set
– egy argumentummal rendelkező függvény, amely a tulajdonság beállításakor hívódik, -
enumerable
– ugyanaz, mint az adattulajdonságoknál, -
configurable
– ugyanaz, mint az adattulajdonságoknál.
Egy fullName
accessor defineProperty
létrehozásához például átadhatunk egy get
és set
leírót:
let user = { name: "John", surname: "Smith"};Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { = value.split(" "); }});alert(user.fullName); // John Smithfor(let key in user) alert(key); // name, surname
Megjegyezzük, hogy egy tulajdonság vagy accessor (get/set
metódusokkal rendelkezik) vagy adattulajdonság (value
-vel rendelkezik) lehet, de mindkettő nem.
Ha ugyanabban a leíróban megpróbáljuk megadni a get
és a value
tulajdonságot is, hiba fog jelentkezni:
// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});
Smarter getterek/setterek
A getterek/setterek “valódi” tulajdonságértékek feletti burkolatként használhatók, hogy nagyobb kontrollt nyerjünk a velük végzett műveletek felett.
Például, ha meg akarjuk tiltani a user
túl rövid neveket, akkor lehet egy name
setterünk, és az értéket egy külön _name
tulajdonságban tarthatjuk:
let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; }};user.name = "Pete";alert(user.name); // Peteuser.name = ""; // Name is too short...
A nevet tehát a _name
tulajdonságban tároljuk, a hozzáférés pedig getter és setter segítségével történik.
Technikailag a külső kód a user._name
használatával közvetlenül hozzáférhet a névhez. De van egy széles körben ismert konvenció, miszerint az "_"
aláhúzással kezdődő tulajdonságok belsőek, és nem érinthetők az objektumon kívülről.
A kompatibilitás érdekében
Az accessorok egyik nagyszerű haszna, hogy lehetővé teszik, hogy egy “rendes” adattulajdonság felett bármikor átvegyük az irányítást egy getterrel és egy setterrel való helyettesítéssel és a viselkedésének finomhangolásával.
Tegyük fel, hogy elkezdtük a felhasználói objektumok megvalósítását az name
és age
adattulajdonságok használatával:
function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25
…De előbb-utóbb változhatnak a dolgok. Lehet, hogy a age
helyett a birthday
tárolása mellett döntünk, mert ez pontosabb és kényelmesebb:
function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));
Most mi legyen a régi kóddal, amely még mindig a age
tulajdonságot használja?
Megpróbálhatjuk megtalálni az összes ilyen helyet és kijavítani őket, de ez időt vesz igénybe és nehéz lehet, ha azt a kódot sok más ember használja. És különben is, a age
egy szép dolog a user
-ben, nem?
Maradjon meg.
Egy getter hozzáadása a age
-hez megoldja a problémát:
function User(name, birthday) { this.name = name; this.birthday = birthday; // age is calculated from the current date and birthday 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 is availablealert( john.age ); // ...as well as the age
Most a régi kód is működik, és van egy szép további tulajdonságunk.