Jak víme, do objektů můžeme ukládat vlastnosti.
Až dosud pro nás vlastnost byla pouhou dvojicí „klíč-hodnota“. Ale vlastnost objektu je ve skutečnosti něco flexibilnějšího a silnějšího.
V této kapitole prostudujeme další možnosti konfigurace a v další se podíváme, jak je neviditelně proměnit na funkce getterů a setterů.
Atributy vlastností
Vlastnosti objektů mají kromě hodnoty value tři speciální atributy (tzv. „vlajky“ nebo „přepínače“):
writable(zapisovatelná) – je-litrue, hodnota vlastnosti může být změněna, jinak je jen pro čtení.enumerable(enumerovatelná) – je-litrue, vlastnost je vyjmenována v cyklech, jinak v nich vyjmenována není.configurable(konfigurovatelná) – je-litrue, vlastnost může být smazána a tyto atributy mohou být měněny, jinak ne.
Prozatím jsme je neviděli, protože se obecně neukazují. Když vytvoříme vlastnost „obvyklým způsobem“, všechny jsou true. Můžeme je však kdykoli změnit.
Nejprve se podíváme, jak tyto přepínače zjistit.
Získat úplnou informaci o vlastnosti nám umožňuje metoda Object.getOwnPropertyDescriptor.
Syntaxe je:
let deskriptor = Object.getOwnPropertyDescriptor(obj, názevVlastnosti);
obj- Objekt, z něhož chceme informaci získat.
názevVlastnosti- Název vlastnosti.
Návratovou hodnotou je tzv. „deskriptor vlastnosti“: objekt, který obsahuje hodnotu a všechny přepínače.
Například:
let uživatel = {
jméno: "Jan"
};
let deskriptor = Object.getOwnPropertyDescriptor(uživatel, 'jméno');
alert( JSON.stringify(deskriptor, null, 2 ) );
/* deskriptor vlastnosti:
{
"value": "Jan",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
Chceme-li přepínače změnit, můžeme použít metodu Object.defineProperty.
Syntaxe je:
Object.defineProperty(obj, názevVlastnosti, deskriptor)
obj,názevVlastnosti- Objekt a jeho vlastnost, na kterou se aplikuje deskriptor.
deskriptor- Deskriptor vlastnosti, který se aplikuje.
Jestliže vlastnost existuje, defineProperty změní její přepínače. V opačném případě tuto vlastnost vytvoří se zadanou hodnotou a přepínači; není-li v takovém případě některý přepínač uveden, předpokládá se, že je false.
Například zde vytvoříme vlastnost jméno se všemi přepínači nepravdivými:
let uživatel = {};
Object.defineProperty(uživatel, "jméno", {
value: "Jan"
});
let deskriptor = Object.getOwnPropertyDescriptor(uživatel, 'jméno');
alert( JSON.stringify(deskriptor, null, 2 ) );
/*
{
"value": "Jan",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
Porovnejte si ji s výše uvedenou vlastností uživatel.jméno „vytvořenou normálně“: nyní jsou všechny přepínače nepravdivé. Pokud to chceme jinak, můžeme je v objektu deskriptor nastavit na true.
Nyní se na efekty přepínačů podívejme na příkladu.
Nezapisovatelná
Učiňme vlastnost uživatel.jméno nezapisovatelnou (nebude moci být změněna) změnou přepínače writable:
let uživatel = {
jméno: "Jan"
};
Object.defineProperty(uživatel, "jméno", {
writable: false
});
uživatel.jméno = "Petr"; // Chyba: Nelze přiřazovat do vlastnosti 'jméno', která je jen pro čtení
Nyní nikdo nemůže změnit jméno našeho uživatele, pokud sám nezavolá metodu defineProperty, která přepíše naše nastavení.
V nestriktním režimu nenastane žádná chyba, když se pokusíme zapsat do nezapisovatelné vlastnosti a podobně. Operace se však stále neprovede. Akce porušující nastavení přepínačů se v nestriktním režimu prostě tiše ignorují.
Zde je stejný příklad, ale vlastnost je vytvořena zcela nově:
let uživatel = { };
Object.defineProperty(uživatel, "jméno", {
value: "Jan",
// u nových vlastností musíme výslovně uvést, co je true
enumerable: true,
configurable: true
});
alert(uživatel.jméno); // Jan
uživatel.jméno = "Petr"; // Chyba
Neenumerovatelná
Přidejme nyní do objektu uživatel vlastní metodu toString.
Normálně je vestavěná metoda toString v objektech neenumerovatelná, v cyklu for..in se neukazuje. Jestliže však přidáme náš vlastní toString, standardně se ve for..in ukáže, například takto:
let uživatel = {
jméno: "Jan",
toString() {
return this.jméno;
}
};
// Standardně budou zobrazeny obě naše vlastnosti:
for (let klíč in uživatel) alert(klíč); // jméno, toString
Pokud se nám to nelíbí, můžeme nastavit enumerable:false. Vlastnost se pak neobjeví v cyklu for..in, stejně jako vestavěná:
let uživatel = {
jméno: "Jan",
toString() {
return this.jméno;
}
};
Object.defineProperty(uživatel, "toString", {
enumerable: false
});
// Nyní náš toString zmizí:
for (let klíč in uživatel) alert(klíč); // jméno
Neenumerovatelné vlastnosti jsou také vyřazeny z metody Object.keys:
alert(Object.keys(uživatel)); // jméno
Nekonfigurovatelná
Přepínač nekonfigurovatelnosti (configurable:false) je někdy přednastaven v zabudovaných objektech a vlastnostech.
Nekonfigurovatelná vlastnost nemůže být smazána a její atributy nemohou být měněny.
Například Math.PI je nezapisovatelná, neenumerovatelná a nekonfigurovatelná:
let deskriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
alert( JSON.stringify(deskriptor, null, 2 ) );
/*
{
"value": 3.141592653589793,
"writable": false,
"enumerable": false,
"configurable": false
}
*/
Programátor tedy nemůže změnit hodnotu Math.PI nebo tuto vlastnost přepsat.
Math.PI = 3; // Chyba, protože má writable: false
// delete Math.PI by rovněž nefungovalo
Nemůžeme ani změnit Math.PI tak, aby byla znovu writable (zapisovatelná):
// Chyba kvůli configurable: false
Object.defineProperty(Math, "PI", { writable: true });
Neexistuje vůbec nic, co bychom mohli s Math.PI dělat.
Nastavení vlastnosti jako nekonfigurovatelné je nevratné. Vlastnost nemůžeme pomocí defineProperty změnit zpět.
Prosíme všimněte si: configurable: false brání ve změnách přepínačů vlastnosti a jejím smazání, ale umožňuje měnit její hodnotu.
Zde je uživatel.jméno nekonfigurovatelná, ale stále ji můžeme měnit (je zapisovatelná):
let uživatel = {
jméno: "Jan"
};
Object.defineProperty(uživatel, "jméno", {
configurable: false
});
uživatel.jméno = "Petr"; // funguje správně
delete uživatel.jméno; // Chyba
A zde učiníme uživatel.jméno „navěky zapečetěnou“ konstantou, právě jako zabudované Math.PI:
let uživatel = {
jméno: "Jan"
};
Object.defineProperty(uživatel, "jméno", {
writable: false,
configurable: false
});
// nebudeme moci změnit uživatel.jméno ani její přepínače
// nic z tohoto nebude fungovat:
uživatel.jméno = "Petr";
delete uživatel.jméno;
Object.defineProperty(uživatel, "jméno", { value: "Petr" });
Ve změně přepínačů je jedna drobná výjimka.
U nekonfigurovatelné vlastnosti můžeme změnit writable: true na false, abychom zabránili modifikaci její hodnoty (abychom přidali další ochrannou vrstvu). Obráceně to však nejde.
Object.defineProperties
Definovat více vlastností najednou nám umožňuje metoda Object.defineProperties(obj, deskriptory).
Syntaxe je:
Object.defineProperties(obj, {
vlastnost1: deskriptor1,
vlastnost2: deskriptor2
// ...
});
Například:
Object.defineProperties(uživatel, {
jméno: { value: "Jan", writable: false },
příjmení: { value: "Novák", writable: false },
// ...
});
Můžeme tedy nastavit více vlastností najednou.
Object.getOwnPropertyDescriptors
Chceme-li získat deskriptory všech vlastností najednou, můžeme použít metodu Object.getOwnPropertyDescriptors(obj).
Společně s metodou Object.defineProperties ji můžeme použít jako způsob klonování objektu, kterým zachováme přepínače:
let klon = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Když běžně klonujeme objekt, kopírujeme vlastnosti přiřazením, například takto:
for (let klíč in uživatel) {
klon[klíč] = uživatel[klíč]
}
…To však nekopíruje přepínače. Chceme-li tedy „lepší“ klon, dáme přednost metodě Object.defineProperties.
Další rozdíl je v tom, že for..in ignoruje symbolické a neenumerovatelné vlastnosti, ale Object.getOwnPropertyDescriptors vrací deskriptory všech vlastností včetně symbolických a neenumerovatelných.
Globální zapečetění objektu
Deskriptory vlastností fungují na úrovni jednotlivých vlastností.
Existují však i metody, které omezují přístup k celému objektu:
- Object.preventExtensions(obj)
- Zakazuje přidávání nových vlastností do objektu.
- Object.seal(obj)
- Zakazuje přidávání a odstraňování vlastností. Nastaví
configurable: falsepro všechny existující vlastnosti. - Object.freeze(obj)
- Zakazuje přidávání, odstraňování a měnění vlastností. Nastaví
configurable: false, writable: falsepro všechny existující vlastnosti.
A existují i testy na ně:
- Object.isExtensible(obj)
- Vrátí
false, je-li zakázáno přidávání vlastností, jinak vrátítrue. - Object.isSealed(obj)
- Vrátí
true, je-li zakázáno přidávání a odstraňování vlastností a všechny existující vlastnosti majíconfigurable: false. - Object.isFrozen(obj)
- Vrátí
true, je-li zakázáno přidávání, odstraňování a měnění vlastností a všechny existující vlastnosti majíconfigurable: false, writable: false.
V praxi se však tyto metody používají jen zřídka.
Komentáře
<code>, pro několik řádků je obalte značkou<pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)