Příznaky a popisy vlastností

Jak víme, objekty mohou ukládat vlastnosti.

Doposud pro nás byla nemovitost jednoduchým párem „klíč–hodnota“. Ale vlastnost objektu je ve skutečnosti flexibilnější a výkonnější věc.

V této kapitole prostudujeme další možnosti konfigurace a v další se podíváme, jak je neviditelně přeměnit na funkce getter/setter.

Příznaky vlastností

Vlastnosti objektu, kromě value , mají tři speciální atributy (takzvané „vlajky“):

  • writable – pokud true , hodnotu lze změnit, jinak je pouze pro čtení.
  • enumerable – pokud true , pak jsou uvedeny v smyčkách, jinak nejsou uvedeny.
  • configurable – pokud true , vlastnost lze smazat a tyto atributy upravit, jinak ne.

Ještě jsme je neviděli, protože se obecně nezobrazují. Když vytvoříme vlastnost „obvyklým způsobem“, všechny jsou true . Ale také je můžeme kdykoli změnit.

Nejprve se podívejme, jak tyto příznaky získat.

Metoda Object.getOwnPropertyDescriptor umožňuje dotaz na úplné informace o nemovitosti.

Syntaxe je:

let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
Objekt, ze kterého se mají získat informace.
propertyName
Název vlastnosti.

Vrácená hodnota je takzvaný objekt „deskriptoru vlastnosti“:obsahuje hodnotu a všechny příznaky.

Například:

let user = {
 name: "John"
};

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
 "value": "John",
 "writable": true,
 "enumerable": true,
 "configurable": true
}
*/

Pro změnu příznaků můžeme použít Object.defineProperty.

Syntaxe je:

Object.defineProperty(obj, propertyName, descriptor)
obj , propertyName
Objekt a jeho vlastnost pro použití deskriptoru.
descriptor
Objekt deskriptoru vlastnosti, který se má použít.

Pokud vlastnost existuje, defineProperty aktualizuje své vlajky. Jinak vytvoří vlastnost s danou hodnotou a příznaky; v takovém případě, pokud příznak není zadán, předpokládá se false .

Zde je například vlastnost name je vytvořen se všemi falešnými příznaky:

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
}
 */

Porovnejte jej s „normálně vytvořeným“ user.name výše:nyní jsou všechny vlajky falešné. Pokud to není to, co chceme, měli bychom je nastavit na true v descriptor .

Nyní se podívejme na účinky vlajek na příkladu.

Bez možnosti zápisu

Udělejme user.name nezapisovatelné (nelze znovu přiřadit) změnou writable příznak:

let user = {
 name: "John"
};

Object.defineProperty(user, "name", {
 writable: false
});

user.name = "Pete"; // Error: Cannot assign to read only property 'name'

Nyní nikdo nemůže změnit jméno našeho uživatele, pokud nepoužije své vlastní defineProperty přepsat naše.

Chyby se objevují pouze v přísném režimu

V non-strict režimu nedochází k žádným chybám při zápisu do nezapisovatelných vlastností a podobně. Operace se ale stále nepovede. Akce porušující příznak jsou pouze tiše ignorovány v non-strict.

Zde je stejný příklad, ale vlastnost je vytvořena od začátku:

let user = { };

Object.defineProperty(user, "name", {
 value: "John",
 // for new properties we need to explicitly list what's true
 enumerable: true,
 configurable: true
});

alert(user.name); // John
user.name = "Pete"; // Error

Nevyčíslitelné

Nyní přidáme vlastní toString na user .

Obvykle je to vestavěný toString pro objekty není vyčíslitelný, nezobrazuje se v for..in . Ale pokud přidáme toString naše vlastní, pak se ve výchozím nastavení zobrazí v for..in , takto:

let user = {
 name: "John",
 toString() {
 return this.name;
 }
};

// By default, both our properties are listed:
for (let key in user) alert(key); // name, toString

Pokud se nám to nelíbí, můžeme nastavit enumerable:false . Pak se nezobrazí v for..in smyčka, stejně jako ta vestavěná:

let user = {
 name: "John",
 toString() {
 return this.name;
 }
};

Object.defineProperty(user, "toString", {
 enumerable: false
});

// Now our toString disappears:
for (let key in user) alert(key); // name

Vlastnosti, které nelze vyčíslit, jsou také vyloučeny z Object.keys :

alert(Object.keys(user)); // name

Nelze konfigurovat

Nekonfigurovatelný příznak (configurable:false ) je někdy přednastaveno pro vestavěné objekty a vlastnosti.

Nekonfigurovatelnou vlastnost nelze smazat, nelze upravit její atributy.

Například Math.PI je nezapisovatelný, nevyčíslitelný a konfigurovatelný:

let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
 "value": 3.141592653589793,
 "writable": false,
 "enumerable": false,
 "configurable": false
}
*/

Programátor tedy nemůže změnit hodnotu Math.PI nebo jej přepište.

Math.PI = 3; // Error, because it has writable: false

// delete Math.PI won't work either

Nemůžeme také změnit Math.PI být writable znovu:

// Error, because of configurable: false
Object.defineProperty(Math, "PI", { writable: true });

S Math.PI nemůžeme dělat absolutně nic .

Udělat nemovitost nekonfigurovatelnou je jednosměrná cesta. Nemůžeme jej změnit zpět pomocí defineProperty .

Poznámka:configurable: false zabraňuje změnám příznaků vlastnosti a jejímu mazání a zároveň umožňuje měnit její hodnotu.

Zde user.name není konfigurovatelný, ale stále jej můžeme změnit (protože je zapisovatelný):

let user = {
 name: "John"
};

Object.defineProperty(user, "name", {
 configurable: false
});

user.name = "Pete"; // works fine
delete user.name; // Error

A tady vytvoříme user.name „navždy zapečetěná“ konstanta, stejně jako vestavěná Math.PI :

let user = {
 name: "John"
};

Object.defineProperty(user, "name", {
 writable: false,
 configurable: false
});

// won't be able to change user.name or its flags
// all this won't work:
user.name = "Pete";
delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
Jediná možná změna atributu:zapisovatelný true → false

Existuje malá výjimka ohledně změny příznaků.

Můžeme změnit writable: true na false pro nekonfigurovatelnou vlastnost, čímž se zabrání změně její hodnoty (pro přidání další vrstvy ochrany). Ne naopak.

Object.defineProperties

Existuje metoda Object.defineProperties(obj, deskriptory), která umožňuje definovat mnoho vlastností najednou.

Syntaxe je:

Object.defineProperties(obj, {
 prop1: descriptor1,
 prop2: descriptor2
 // ...
});

Například:

Object.defineProperties(user, {
 name: { value: "John", writable: false },
 surname: { value: "Smith", writable: false },
 // ...
});

Můžeme tedy nastavit mnoho vlastností najednou.

Object.getOwnPropertyDescriptors

Chcete-li získat všechny deskriptory vlastností najednou, můžeme použít metodu Object.getOwnPropertyDescriptors(obj).

Společně s Object.defineProperties lze jej použít jako způsob klonování objektu „s vědomím příznaků“:

let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

Normálně, když klonujeme objekt, používáme ke kopírování vlastností přiřazení, jako je toto:

for (let key in user) {
 clone[key] = user[key]
}

…To ale nekopíruje vlajky. Pokud tedy chceme „lepší“ klon, pak Object.defineProperties je preferováno.

Dalším rozdílem je, že for..in ignoruje symbolické a nevyčíslitelné vlastnosti, ale Object.getOwnPropertyDescriptors vrátí vše deskriptory vlastností včetně symbolických a nevyčíslitelných.

Globální utěsnění objektu

Deskriptory vlastností fungují na úrovni jednotlivých vlastností.

Existují také metody, které omezují přístup k celku objekt:

Object.preventExtensions(obj)
Zakazuje přidávání nových vlastností k objektu.
Object.seal(obj)
Zakazuje přidávání/odebírání vlastností. Nastaví configurable: false pro všechny existující vlastnosti.
Object.freeze(obj)
Zakazuje přidávání/odebírání/změnu vlastností. Nastaví configurable: false, writable: false pro všechny existující vlastnosti.

A také pro ně existují testy:

Object.isExtensible(obj)
Vrátí false pokud je přidávání vlastností zakázáno, jinak true .
Object.isSealed(obj)
Vrátí true pokud je přidávání/odebírání vlastností zakázáno a všechny existující vlastnosti mají configurable: false .
Object.isFrozen(obj)
Vrátí true pokud je přidávání/odebírání/změna vlastností zakázáno a všechny aktuální vlastnosti jsou configurable: false, writable: false .

Tyto metody se v praxi používají jen zřídka.