Existují dva druhy vlastností objektu.
Prvním druhem jsou datové vlastnosti. S nimi již umíme pracovat. Všechny vlastnosti, které jsme dosud používali, byly datové vlastnosti.
Druhý typ vlastností je něco nového. Jsou to vlastnosti přístupové. Jsou to v podstatě funkce, které se provádějí při získání a nastavení hodnoty, ale pro vnější kód vypadají jako běžné vlastnosti.
Gettery a settery
Vlastnosti accessor jsou reprezentovány metodami „getter“ a „setter“. V objektovém literálu jsou označeny get
a set
:
let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};
Getter funguje při čtení obj.propName
, setter při přiřazování.
Například máme objekt user
s name
a surname
:
let user = { name: "John", surname: "Smith"};
Nyní chceme přidat vlastnost fullName
, která by měla být "John Smith"
. Samozřejmě nechceme kopírovat-vkládat existující informace, takže ji můžeme implementovat jako accessor:
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith
Zvenčí vypadá vlastnost accessor jako běžná. To je myšlenka vlastností accessor. Vlastnost user.fullName
nevoláme jako funkci, ale normálně ji čteme: getter běží za scénou.
Od této chvíle má fullName
pouze getter. Pokud se pokusíme přiřadit user.fullName=
, dojde k chybě:
let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)
Napravíme to přidáním setteru pro user.fullName
:
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
Výsledkem je „virtuální“ vlastnost fullName
. Je čitelná a zapisovatelná.
Deskriptory přístupových vlastností
Deskriptory pro přístupové vlastnosti se liší od deskriptorů pro datové vlastnosti.
Pro přístupové vlastnosti neexistuje value
nebo writable
, ale místo toho existují funkce get
a set
.
To znamená, že deskriptor accessoru může mít:
-
get
– funkci bez argumentů, která funguje při čtení vlastnosti, -
set
– funkci s jedním argumentem, která se volá při nastavení vlastnosti, -
enumerable
– stejně jako u datových vlastností, -
configurable
– stejně jako u datových vlastností.
Příklad pro vytvoření accessoru fullName
s defineProperty
můžeme předat deskriptor s get
a set
:
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
Upozorňujeme, že vlastnost může být buď accessor (má metody get/set
), nebo datová vlastnost (má value
), nikoli obojí.
Pokusíme-li se v jednom deskriptoru zadat jak get
, tak value
, dojde k chybě:
// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});
Silnější gettery/settery
Gettery/settery lze použít jako obal nad „skutečnými“ hodnotami vlastností a získat tak větší kontrolu nad operacemi s nimi.
Pokud například chceme zakázat příliš krátká jména pro user
, můžeme mít setter name
a hodnotu uchovávat v samostatné vlastnosti _name
:
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...
Takže jméno je uloženo ve vlastnosti _name
a přístup se provádí přes getter a setter.
Technicky je externí kód schopen přistupovat k názvu přímo pomocí user._name
. Existuje však všeobecně známá konvence, že vlastnosti začínající podtržítkem "_"
jsou interní a nemělo by se na ně sahat z vnějšku objektu.
Použití pro kompatibilitu
Jedním z velkých využití accessorů je, že umožňují kdykoli převzít kontrolu nad „běžnou“ datovou vlastností tím, že ji nahradí getterem a setterem a upraví její chování.
Představte si, že bychom začali implementovat uživatelské objekty pomocí datových vlastností name
a age
:
function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25
… Dříve nebo později se však věci mohou změnit. Místo age
se možná rozhodneme ukládat birthday
, protože je to přesnější a pohodlnější:
function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));
Teď co dělat se starým kódem, který stále používá vlastnost age
?
Můžeme se pokusit najít všechna taková místa a opravit je, ale to zabere čas a může to být obtížné, pokud tento kód používá mnoho dalších lidí. A kromě toho je age
v user
pěkná věc, ne?“
Ponechme si ji.
Přidání getteru pro age
problém vyřeší:
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
Teď funguje i starý kód a my máme pěknou vlastnost navíc.