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…)