ProzatÃm jsme se nauÄili dvÄma následujÃcÃm složitým datovým strukturám:
- Objekty se použÃvajà k ukládánà kolekcà hodnot pod klÃÄi.
- Pole se použÃvajà k ukládánà seÅazených kolekcÃ.
Pro skuteÄný život to vÅ¡ak nestaÄÃ. Proto existujà také Map (mapa) a Set (množina).
Mapa
Map je kolekce datových prvků uložených pod klÃÄi, podobnÄ jako Object. Hlavnà rozdÃl je vÅ¡ak v tom, že Map umožÅuje klÃÄe libovolného typu.
Jejà metody a vlastnosti jsou:
new Map()â vytvoÅà mapu.map.set(klÃÄ, hodnota)â uložà hodnotuhodnotapod klÃÄemklÃÄ.map.get(klÃÄ)â vrátà hodnotu uloženou pod klÃÄemklÃÄ, jestližeklÃÄv mapÄ neexistuje, vrátÃundefined.map.has(klÃÄ)â vrátÃtrue, jestližeklÃÄv mapÄ existuje, jinakfalse.map.delete(klÃÄ)â odstranà prvek (dvojici klÃÄ/hodnota) uložený pod klÃÄemklÃÄ.map.clear()â odstranà z mapy vÅ¡echny prvky.map.sizeâ vrátà aktuálnà poÄet prvků.
PÅÃklad:
let mapa = new Map();
mapa.set('1', 'ÅetÄzec1'); // ÅetÄzcový klÃÄ
mapa.set(1, 'ÄÃslo1'); // ÄÃselný klÃÄ
mapa.set(true, 'bool1'); // booleovský klÃÄ
// pamatujete si na obvyklý Object? ten pÅevádà klÃÄe na ÅetÄzce
// Map si pamatuje typ klÃÄe, takže tyto dva klÃÄe jsou rozdÃlné:
alert( mapa.get(1) ); // 'ÄÃslo1'
alert( mapa.get('1') ); // 'ÅetÄzec1'
alert( mapa.size ); // 3
Jak vidÃme, na rozdÃl od objektů zde nejsou klÃÄe pÅevádÄny na ÅetÄzce. Jsou povoleny klÃÄe jakýchkoli typů.
mapa[klÃÄ] nenà správný způsob, jak použÃvat mapuAÄkoli mapa[klÃÄ] funguje také, napÅ. můžeme nastavit mapa[klÃÄ] = 2, v tomto pÅÃpadÄ se s mapou zacházà jako s planým JavaScriptovým objektem, takže zde platà vÅ¡echna pÅÃsluÅ¡ná omezenà (jen ÅetÄzcové/symbolické klÃÄe a podobnÄ).
MÄli bychom tedy použÃvat metody mapy: set, get a tak dále.
Mapa může použÃvat jako klÃÄe i objekty.
PÅÃklad:
let jan = { jméno: "Jan" };
// pro každého uživatele budeme ukládat poÄet jeho návÅ¡tÄv
let mapaPoÄetNávÅ¡tÄv = new Map();
// jan je klÃÄ mapy
mapaPoÄetNávÅ¡tÄv.set(jan, 123);
alert( mapaPoÄetNávÅ¡tÄv.get(jan) ); // 123
PoužÃvánà objektů jako klÃÄů je jedna z nejpozoruhodnÄjÅ¡Ãch a nejdůležitÄjÅ¡Ãch vlastnostà map. Pro Object to neplatÃ. ÅetÄzec jako klÃÄ objektu je správnÄ, ale jako klÃÄ objektu nemůžeme použÃt jiný Object.
Zkusme to:
let jan = { jméno: "Jan" };
let ben = { jméno: "Ben" };
let objPoÄetNávÅ¡tÄv = {}; // zkusÃme použÃt objekt
objPoÄetNávÅ¡tÄv[ben] = 234; // zkusÃme použÃt jako klÃÄ objekt ben
objPoÄetNávÅ¡tÄv[jan] = 123; // zkusÃme použÃt jako klÃÄ objekt jan, objekt ben bude nahrazen
// Toto bude zapsáno!
alert( objPoÄetNávÅ¡tÄv["[object Object]"] ); // 123
Jelikož objPoÄetNávÅ¡tÄv je objekt, pÅevede vÅ¡echny klÃÄe typu Object, napÅ. uvedené jan a ben, na stejný ÅetÄzec "[object Object]". To rozhodnÄ nenà to, co jsme chtÄli.
Map porovnává klÃÄePro testovánà ekvivalence klÃÄů Map použÃvá algoritmus SameValueZero. Je témÄÅ stejný jako striktnà rovnost ===, ale rozdÃl spoÄÃvá v tom, že NaN se považuje za rovné NaN. Jako klÃÄ tedy můžeme použÃt i NaN.
Tento algoritmus nemůžeme zmÄnit nebo si ho pÅizpůsobit.
Každé volánà mapa.set vrátà samotnou mapu, takže volánà můžeme âzÅetÄzitâ:
mapa.set('1', 'ÅetÄzec1')
.set(1, 'ÄÃslo1')
.set(true, 'bool1');
Iterace nad mapou
Pro procházenà prvků mapy existujà 3 metody:
mapa.keys()â vrátà iterovatelný objekt klÃÄů,mapa.values()â vrátà iterovatelný objekt hodnot,mapa.entries()â vrátà iterovatelný objekt dvojic[klÃÄ, hodnota], použÃvá se standardnÄ vefor..of.
PÅÃklad:
let mapaRecept = new Map([
['okurky', 500],
['rajÄata', 350],
['cibule', 50]
]);
// iterace nad klÃÄi (zelenina)
for (let zelenina of mapaRecept.keys()) {
alert(zelenina); // okurky, rajÄata, cibule
}
// iterace nad hodnotami (množstvÃ)
for (let množstvà of mapaRecept.values()) {
alert(množstvÃ); // 500, 350, 50
}
// iterace nad dvojicemi [klÃÄ, hodnota]
for (let dvojice of mapaRecept) { // totéž jako mapaRecept.entries()
alert(dvojice); // okurky,500 (a tak dále)
}
Iterace probÃhá ve stejném poÅadÃ, v jakém byly hodnoty vloženy. Map toto poÅadà na rozdÃl od Object zachovává.
NavÃc Map obsahuje vestavÄnou metodu forEach, podobnÄ jako Array:
// spustà tuto funkci pro každou dvojici (klÃÄ, hodnota)
mapaRecepty.forEach( (hodnota, klÃÄ, mapa) => {
alert(`${klÃÄ}: ${hodnota}`); // okurky: 500 atd.
});
Object.entries: mapa z objektu
Když je vytvoÅena mapa, můžeme do nà pro inicializaci pÅedat pole (nebo jiný iterovatelný objekt) dvojic klÃÄ/hodnota, napÅÃklad:
// pole dvojic [klÃÄ, hodnota]
let mapa = new Map([
['1', 'ÅetÄzec1'],
[1, 'ÄÃslo1'],
[true, 'bool1']
]);
alert( mapa.get('1') ); // ÅetÄzec1
Máme-li planý objekt a rádi bychom z nÄj vytvoÅili mapu, můžeme použÃt vestavÄnou metodu Object.entries(obj), která vracà pole dvojic klÃÄ/hodnota objektu pÅesnÄ v tomto formátu.
Můžeme tedy vytvoÅit mapu z objektu takto:
let obj = {
jméno: "Jan",
vÄk: 30
};
let mapa = new Map(Object.entries(obj));
alert( mapa.get('jméno') ); // Jan
Zde Object.entries vracà pole dvojic klÃÄ/hodnota: [ ["jméno","Jan"], ["vÄk", 30] ]. To je pÅesnÄ to, co potÅebuje Map.
Object.fromEntries: objekt z mapy
PrávÄ jsme vidÄli, jak vytvoÅit Map z planého objektu pomocà Object.entries(obj).
Existuje i metoda Object.fromEntries, která provádà opak â když jà pÅedáme pole dvojic [klÃÄ, hodnota], vytvoÅà z nÄj objekt:
let ceny = Object.fromEntries([
['banán', 1],
['pomeranÄ', 2],
['maso', 4]
]);
// nynà ceny = { banán: 1, pomeranÄ: 2, maso: 4 }
alert(ceny.pomeranÄ); // 2
PoužitÃm Object.fromEntries můžeme zÃskat z mapy planý objekt.
NapÅÃklad uložÃme do mapy data, ale potÅebujeme je pÅedat kódu tÅetà strany, který oÄekává planý objekt.
Postupujeme takto:
let mapa = new Map();
mapa.set('banán', 1);
mapa.set('pomeranÄ', 2);
mapa.set('maso', 4);
let obj = Object.fromEntries(mapa.entries()); // vytvoÅà planý objekt (*)
// hotovo!
// obj = { banán: 1, pomeranÄ: 2, maso: 4 }
alert(obj.pomeranÄ); // 2
Volánà mapa.entries() vracà iterovatelný objekt dvojic klÃÄ/hodnota, pÅesnÄ ve správném formátu pro Object.fromEntries.
Åádek (*) můžeme také zkrátit:
let obj = Object.fromEntries(mapa); // vypustÃme .entries()
To je totéž, protože Object.fromEntries oÄekává jako argument iterovatelný objekt, ne nutnÄ pole. A standardnà iterace mapy vracà stejné dvojice klÃÄ/hodnota jako mapa.entries(). Dostaneme tedy planý objekt se stejnými dvojicemi klÃÄ/hodnota, jaké obsahuje mapa.
Množina
Množina Set je speciálnà typ kolekce â âmnožina hodnotâ (bez klÃÄů), v nÞ se každá hodnota může vyskytnout pouze jednou.
Jejà hlavnà metody jsou:
new Set([iterovatelnýObjekt])â vytvoÅà množinu, a je-li poskytnutiterovatelnýObjekt(obvykle pole), zkopÃruje do nà hodnoty z tohoto objektu.množina.add(hodnota)â pÅidá hodnotuhodnota, vrátà samotnou množinu.množina.delete(hodnota)â odstranà hodnotuhodnota, vrátÃtrue, jestližehodnotav okamžiku volánà v množinÄ existovala, jinakfalse.množina.has(hodnota)â vrátÃtrue, jestliže hodnotahodnotav množinÄ existuje, jinakfalse.množina.clear()â odstranà z množiny vÅ¡echny hodnoty.množina.sizeâ je poÄet hodnot.
Hlavnà vlastnostà množiny je, že opakovaná volánà množina.add(hodnota) se stejnou hodnotou nic neudÄlajÃ. To je důvod, proÄ se každá hodnota v množinÄ objevà pouze jednou.
NapÅÃklad máme pÅicházejÃcà návÅ¡tÄvnÃky a rádi bychom si je vÅ¡echny pamatovali. AvÅ¡ak opakované návÅ¡tÄvy by nemÄly vést ke zdvojenÃ. Každý návÅ¡tÄvnÃk musà být âzapoÄÃtánâ jen jednou.
Set je pro tento úÄel to pravé:
let množina = new Set();
let jan = { jméno: "Jan" };
let petr = { jméno: "Petr" };
let marie = { jméno: "Marie" };
// návÅ¡tÄvy, nÄkteÅà uživatelé pÅiÅ¡li vÃcekrát
množina.add(jan);
množina.add(petr);
množina.add(marie);
množina.add(jan);
množina.add(marie);
// množina si pamatuje jen unikátnà hodnoty
alert( množina.size ); // 3
for (let uživatel of množina) {
alert(uživatel.jméno); // Jan (pak Petr a Marie)
}
Alternativou pro Set by mohlo být pole uživatelů a kód, který pÅi každém vloženà hledá duplikáty pomocà pole.find. Výkon by vÅ¡ak byl mnohem horÅ¡Ã, protože tato metoda procházà celým polem a ovÄÅuje každý prvek. Set je internÄ pro kontrolu unikátnosti mnohem lépe optimalizována.
Iterace nad množinou
Množinu můžeme procházet buÄ pomocà for..of, nebo pomocà forEach:
let množina = new Set(["pomeranÄe", "jablka", "banány"]);
for (let hodnota of množina) alert(hodnota);
// totéž s forEach:
množina.forEach((hodnota, hodnotaZnovu, množina) => {
alert(hodnota);
});
VÅ¡imnÄte si nÄÄeho veselého. Funkce pÅedávaná do forEach má 3 argumenty: hodnota, pak stejnou hodnotu hodnotaZnovu a pak cÃlový objekt. Opravdu, stejná hodnota se v argumentech objevuje dvakrát.
To sloužà ke kompatibilitÄ s Map, v nÞ funkce pÅedávaná do forEach má tÅi argumenty. JistÄ, vypadá to trochu zvláštnÄ. Může to vÅ¡ak pomoci v nÄkterých pÅÃpadech snadno nahradit mapu množinou a naopak.
Množina také poskytuje stejné metody, jaké má Map pro iterátory:
množina.keys()â vrátà iterovatelný objekt s hodnotami,množina.values()â totéž jakomnožina.keys(), existuje kvůli kompatibilitÄ sMap,množina.entries()â vrátà iterovatelný objekt s dvojicemi[hodnota, hodnota], existuje kvůli kompatibilitÄ sMap.
ShrnutÃ
Map â je kolekce hodnot s klÃÄi.
Metody a vlastnosti:
new Map([iterovatelnýObjekt])â vytvoÅà mapu, nepovinný objektiterovatelnýObjekt(napÅ. pole) obsahuje dvojice[klÃÄ,hodnota]pro inicializaci.mapa.set(klÃÄ, hodnota)â uložà hodnotuhodnotapod klÃÄemklÃÄ, vrátà samotnou mapu.mapa.get(klÃÄ)â vrátà hodnotu uloženou pod klÃÄemklÃÄ, jestližeklÃÄv mapÄ neexistuje, vrátÃundefined.mapa.has(klÃÄ)â vrátÃtrue, jestližeklÃÄv mapÄ existuje, jinakfalse.mapa.delete(klÃÄ)â odstranà prvek uložený pod klÃÄemklÃÄ, vrátÃtrue, jestližeklÃÄv okamžiku volánà v mapÄ existoval, jinakfalse.mapa.clear()â odstranà z mapy vÅ¡echny prvky.mapa.sizeâ vrátà aktuálnà poÄet prvků.
RozdÃly oproti bÄžnému objektu:
- KlÃÄe mohou být libovolného typu vÄetnÄ objektů.
- Obsahuje dalšà užiteÄné metody, vlastnost
size.
Set â je kolekce unikátnÃch hodnot.
Metody a vlastnosti:
new Set(iterovatelnýObjekt)â vytvoÅà množinu, nepovinný objektiterovatelnýObjekt(napÅ. pole) obsahuje hodnoty pro inicializaci.množina.add(hodnota)â pÅidá hodnotuhodnota(pokudhodnotajiž existuje, neudÄlá nic), vrátà samotnou množinu.množina.delete(hodnota)â odstranà hodnotuhodnota, vrátÃtrue, jestližehodnotav okamžiku volánà v množinÄ existovala, jinakfalse.množina.has(hodnota)â vrátÃtrue, jestliže hodnotahodnotav množinÄ existuje, jinakfalse.množina.clear()â odstranà z množiny vÅ¡echny hodnoty.množina.sizeâ je poÄet hodnot.
Iterace nad mapou a množinou probÃhá vždy ve stejném poÅadÃ, v jakém byly prvky vloženy. Nemůžeme tedy ÅÃci, že tyto kolekce nejsou seÅazené, ale nemůžeme prvky seÅadit jinak ani pÅÃmo zÃskat prvek na urÄitém poÅadÃ.
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â¦)