Typ symbolu

Podle specifikace mohou jako klíče vlastností objektu sloužit pouze dva primitivní typy:

  • typ řetězce nebo
  • typ symbolu.

V opačném případě, pokud jeden používá jiný typ, například číslo, je automaticky převeden na řetězec. Takže obj[1] je stejný jako obj["1"] a obj[true] je stejný jako obj["true"] .

Doposud jsme používali pouze řetězce.

Nyní pojďme prozkoumat symboly a podívat se, co pro nás mohou udělat.

Symboly

„Symbol“ představuje jedinečný identifikátor.

Hodnotu tohoto typu lze vytvořit pomocí Symbol() :

let id = Symbol();

Po vytvoření můžeme dát symbolům popis (také nazývaný název symbolu), většinou užitečný pro účely ladění:

// id is a symbol with the description "id"
let id = Symbol("id");

Symboly jsou zaručeně jedinečné. I když vytvoříme mnoho symbolů s naprosto stejným popisem, jedná se o různé hodnoty. Popis je pouze štítek, který nic neovlivňuje.

Zde jsou například dva symboly se stejným popisem – nejsou si rovny:

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

Pokud znáte Ruby nebo jiný jazyk, který má také nějaké „symboly“ – nenechte se zmást. Symboly JavaScriptu jsou různé.

Abychom to shrnuli, symbol je „primitivní jedinečná hodnota“ s volitelným popisem. Podívejme se, kde je můžeme použít.

Symboly se automaticky nepřevádějí na řetězec

Většina hodnot v JavaScriptu podporuje implicitní převod na řetězec. Můžeme například alert téměř jakoukoli hodnotu a bude to fungovat. Symboly jsou speciální. Nepřevádějí se automaticky.

Například toto alert zobrazí chybu:

let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string

To je „ochrana jazyka“ proti nepořádku, protože řetězce a symboly jsou zásadně odlišné a neměly by se náhodně převádět na jiné.

Pokud opravdu chceme zobrazit symbol, musíme explicitně zavolat .toString() na to, jako zde:

let id = Symbol("id");
alert(id.toString()); // Symbol(id), now it works

Nebo získejte symbol.description vlastnost pro zobrazení pouze popisu:

let id = Symbol("id");
alert(id.description); // id

Vlastnosti „Skryté“

Symboly nám umožňují vytvářet „skryté“ vlastnosti objektu, ke kterým žádná jiná část kódu nemůže náhodně přistupovat nebo je přepsat.

Pokud například pracujeme s user objekty, které patří do kódu třetí strany. Rádi bychom k nim přidali identifikátory.

Použijme k tomu symbolový klíč:

let user = { // belongs to another code
 name: "John"
};

let id = Symbol("id");

user[id] = 1;

alert( user[id] ); // we can access the data using the symbol as the key

Jaká je výhoda použití Symbol("id") přes řetězec "id" ?

Jako user objekty patří do jiné kódové základny, není bezpečné k nim přidávat pole, protože bychom mohli ovlivnit předdefinované chování v této jiné kódové základně. K symbolům však nelze přistupovat náhodně. Kód třetí strany nebude znát nově definované symboly, takže je bezpečné přidat symboly do user objektů.

Představte si také, že jiný skript chce mít svůj vlastní identifikátor uvnitř user , pro své vlastní účely.

Potom může tento skript vytvořit svůj vlastní Symbol("id") , takto:

// ...
let id = Symbol("id");

user[id] = "Their id value";

Mezi našimi a jejich identifikátory nebude žádný konflikt, protože symboly se vždy liší, i když mají stejný název.

…Ale pokud bychom použili řetězec "id" místo symbolu pro stejný účel, pak tam by být v konfliktu:

let user = { name: "John" };

// Our script uses "id" property
user.id = "Our id value";

// ...Another script also wants "id" for its purposes...

user.id = "Their id value"
// Boom! overwritten by another script!

Symboly v objektovém literálu

Pokud chceme použít symbol v objektovém literálu {...} , potřebujeme kolem něj hranaté závorky.

Takhle:

let id = Symbol("id");

let user = {
 name: "John",
 [id]: 123 // not "id": 123
};

Je to proto, že potřebujeme hodnotu z proměnné id jako klíč, nikoli řetězec „id“.

Symboly jsou přeskočeny pro…in

Symbolické vlastnosti nejsou součástí for..in smyčka.

Například:

let id = Symbol("id");
let user = {
 name: "John",
 age: 30,
 [id]: 123
};

for (let key in user) alert(key); // name, age (no symbols)

// the direct access by the symbol works
alert( "Direct: " + user[id] ); // Direct: 123

Object.keys(user) je také ignoruje. To je součástí obecného principu „skrytí symbolických vlastností“. Pokud se nad naším objektem zacyklí jiný skript nebo knihovna, nezíská neočekávaně přístup k symbolické vlastnosti.

Naproti tomu Object.assign zkopíruje vlastnosti řetězce i symbolu:

let id = Symbol("id");
let user = {
 [id]: 123
};

let clone = Object.assign({}, user);

alert( clone[id] ); // 123

Tady nejde o žádný paradox. To je záměrně. Myšlenka je taková, že když klonujeme objekt nebo slučujeme objekty, obvykle chceme všechny vlastnosti, které se mají zkopírovat (včetně symbolů jako id ).

Globální symboly

Jak jsme viděli, obvykle se všechny symboly liší, i když mají stejný název. Někdy však chceme, aby stejnojmenné symboly byly stejné entity. Například různé části naší aplikace chtějí získat přístup k symbolu "id" což znamená přesně stejnou vlastnost.

K dosažení tohoto cíle existuje globální registr symbolů . Můžeme v něm vytvářet symboly a přistupovat k nim později a zaručuje, že opakované přístupy se stejným názvem vrátí přesně stejný symbol.

Chcete-li přečíst (pokud chybí) symbol z registru, použijte Symbol.for(key) .

Toto volání zkontroluje globální registr a zjistí, zda existuje symbol popsaný jako key , pak jej vrátí, jinak vytvoří nový symbol Symbol(key) a uloží jej do registru podle daného key .

Například:

// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created

// read it again (maybe from another part of the code)
let idAgain = Symbol.for("id");

// the same symbol
alert( id === idAgain ); // true

Symboly v registru se nazývají globální symboly . Pokud chceme symbol pro celou aplikaci, dostupný všude v kódu – k tomu slouží.

To zní jako Ruby

V některých programovacích jazycích, jako je Ruby, existuje pro každé jméno jeden symbol.

V JavaScriptu, jak vidíme, to platí pro globální symboly.

Symbol.keyFor

Viděli jsme, že pro globální symboly je to Symbol.for(key) vrátí symbol podle názvu. Pro opak – vrátit jméno pomocí globálního symbolu – můžeme použít:Symbol.keyFor(sym) :

Například:

// get symbol by name
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// get name by symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

Symbol.keyFor interně používá globální registr symbolů k vyhledání klíče pro symbol. Takže to nefunguje pro neglobální symboly. Pokud symbol není globální, nebude jej moci najít a vrátí undefined .

To znamená, že všechny symboly mají description vlastnost.

Například:

let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");

alert( Symbol.keyFor(globalSymbol) ); // name, global symbol
alert( Symbol.keyFor(localSymbol) ); // undefined, not global

alert( localSymbol.description ); // name

Symboly systému

Existuje mnoho „systémových“ symbolů, které JavaScript interně používá, a můžeme je použít k doladění různých aspektů našich objektů.

Jsou uvedeny ve specifikaci v tabulce Známé symboly:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • …a tak dále.

Například Symbol.toPrimitive nám umožňuje popsat objekt na primitivní konverzi. Jeho použití uvidíme velmi brzy.

Další symboly budou také známé, když budeme studovat odpovídající jazykové vlastnosti.

Shrnutí

Symbol je primitivní typ pro jedinečné identifikátory.

Symboly jsou vytvořeny pomocí Symbol() volání s volitelným popisem (jméno).

Symboly mají vždy různé hodnoty, i když mají stejný název. Pokud chceme, aby se stejnojmenné symboly rovnaly, měli bychom použít globální registr:Symbol.for(key) vrátí (v případě potřeby vytvoří) globální symbol s key jako jméno. Vícenásobná volání Symbol.for se stejným key vrátí přesně stejný symbol.

Symboly mají dva hlavní případy použití:

  1. „Skryté“ vlastnosti objektu.

    Pokud chceme přidat vlastnost do objektu, který „patří“ do jiného skriptu nebo knihovny, můžeme vytvořit symbol a použít jej jako klíč vlastnosti. Symbolická vlastnost se v for..in nezobrazuje , takže nebude náhodně zpracován společně s jinými vlastnostmi. Také k němu nebude přímý přístup, protože jiný skript nemá náš symbol. Vlastnost tak bude chráněna před náhodným použitím nebo přepsáním.

    Můžeme tedy „skrytě“ skrýt něco do objektů, které potřebujeme, ale ostatní by to neměli vidět, pomocí symbolických vlastností.

  2. JavaScript používá mnoho systémových symbolů, které jsou přístupné jako Symbol.* . Můžeme je použít ke změně některých vestavěných chování. Například později v tutoriálu použijeme Symbol.iterator pro iterable, Symbol.toPrimitive k nastavení konverze z objektu na primitivní atd.

Technicky nejsou symboly 100% skryté. Existuje vestavěná metoda Object.getOwnPropertySymbols(obj), která nám umožňuje získat všechny symboly. Existuje také metoda s názvem Reflect.ownKeys(obj), která vrací vše klíče objektu včetně symbolických. Ale většina knihoven, vestavěných funkcí a syntaktických konstrukcí tyto metody nepoužívá.