Doposud jsme se dozvěděli o následujících komplexních datových strukturách:
- Objekty se používají k ukládání klíčovaných kolekcí.
- Pole se používají k ukládání objednaných kolekcí.
Ale to pro skutečný život nestačí. Proto Map
a Set
také existují.
Mapa
Mapa je sbírka klíčovaných datových položek, stejně jako Object
. Ale hlavní rozdíl je v tom, že Map
umožňuje klíče jakéhokoli typu.
Metody a vlastnosti jsou:
new Map()
– vytvoří mapu.map.set(key, value)
– uloží hodnotu pomocí klíče.map.get(key)
– vrátí hodnotu pomocí klíče,undefined
pokudkey
na mapě neexistuje.map.has(key)
– vrátítrue
pokudkey
existuje,false
jinak.map.delete(key)
– odebere hodnotu pomocí klíče.map.clear()
– odstraní vše z mapy.map.size
– vrátí aktuální počet prvků.
Například:
let map = new Map();
map.set('1', 'str1'); // a string key
map.set(1, 'num1'); // a numeric key
map.set(true, 'bool1'); // a boolean key
// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
Jak vidíme, na rozdíl od objektů se klíče nepřevádějí na řetězce. Je možný jakýkoli typ klíče.
map[key]
není správný způsob použití Map
Ačkoli map[key]
funguje také např. můžeme nastavit map[key] = 2
, jedná se o ošetření map
jako prostý objekt JavaScriptu, takže to znamená všechna odpovídající omezení (pouze řetězcové/symbolové klíče a tak dále).
Měli bychom tedy použít map
metody:set
, get
a tak dále.
Mapa může také používat objekty jako klíče.
Například:
let john = { name: "John" };
// for every user, let's store their visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
Použití objektů jako klíčů je jedním z nejpozoruhodnějších a nejdůležitějších Map
funkce. Totéž se nepočítá pro Object
. Řetězec jako klíč v Object
je v pořádku, ale nemůžeme použít jiný Object
jako klíč v Object
.
Zkusme to:
let john = { name: "John" };
let ben = { name: "Ben" };
let visitsCountObj = {}; // try to use an object
visitsCountObj[ben] = 234; // try to use ben object as the key
visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced
// That's what got written!
alert( visitsCountObj["[object Object]"] ); // 123
Jako visitsCountObj
je objekt, převede všechny Object
klíče, například john
a ben
výše, na stejný řetězec "[object Object]"
. Rozhodně ne to, co chceme.
Map
porovnává klíče
Chcete-li otestovat ekvivalenci klíčů, Map
používá algoritmus SameValueZero. Je to zhruba stejné jako přísná rovnost ===
, ale rozdíl je v tom, že NaN
je považováno za rovné NaN
. Takže NaN
lze použít také jako klíč.
Tento algoritmus nelze změnit ani přizpůsobit.
Řetězení
Každých map.set
call vrátí samotnou mapu, takže můžeme hovory „řetězit“:
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
Iterace přes mapu
Pro smyčkování přes map
, existují 3 způsoby:
map.keys()
– vrátí iterovatelnou hodnotu pro klíče,map.values()
– vrací iterovatelnou hodnotu pro hodnoty,map.entries()
– vrátí iterovatelnou hodnotu pro položky[key, value]
, ve výchozím nastavení se používá vfor..of
.
Například:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// iterate over values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
alert(entry); // cucumber,500 (and so on)
}
Použije se objednávka vložení
Iterace probíhá ve stejném pořadí, v jakém byly vloženy hodnoty. Map
zachovává toto pořadí, na rozdíl od běžného Object
.
Kromě toho Map
má vestavěný forEach
metoda, podobná Array
:
// runs the function for each (key, value) pair
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Object.entries:Mapa z objektu
Když Map
je vytvořen, můžeme předat pole (nebo jinou iterovatelnou) s páry klíč/hodnota pro inicializaci, takto:
// array of [key, value] pairs
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
Pokud máme prostý objekt a chtěli bychom vytvořit Map
z něj pak můžeme použít vestavěnou metodu Object.entries(obj), která vrací pole párů klíč/hodnota pro objekt přesně v tomto formátu.
Můžeme tedy vytvořit mapu z objektu, jako je tento:
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
Zde Object.entries
vrátí pole párů klíč/hodnota:[ ["name","John"], ["age", 30] ]
. To je to, co Map
potřeby.
Object.fromEntries:Objekt z mapy
Právě jsme viděli, jak vytvořit Map
z prostého objektu s Object.entries(obj)
.
Je tam Object.fromEntries
metoda, která dělá opak:je dána polem [key, value]
párů, vytvoří z nich objekt:
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// now prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
Můžeme použít Object.fromEntries
získat prostý objekt z Map
.
Např. data ukládáme do Map
, ale musíme jej předat kódu třetí strany, který očekává prostý objekt.
Tady to je:
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // make a plain object (*)
// done!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
Volání na číslo map.entries()
vrátí iterovatelný pár klíč/hodnota přesně ve správném formátu pro Object.fromEntries
.
Můžeme také vytvořit řádek (*)
kratší:
let obj = Object.fromEntries(map); // omit .entries()
To je stejné, protože Object.fromEntries
očekává iterovatelný objekt jako argument. Ne nutně pole. A standardní iterace pro map
vrátí stejné páry klíč/hodnota jako map.entries()
. Dostaneme tedy prostý objekt se stejnými hodnotami klíč/hodnota jako map
.
Nastavit
A Set
je speciální typová kolekce – „množina hodnot“ (bez klíčů), kde se každá hodnota může vyskytovat pouze jednou.
Jeho hlavní metody jsou:
new Set(iterable)
– vytvoří sadu, a pokuditerable
objekt (obvykle pole), zkopíruje z něj hodnoty do sady.set.add(value)
– přidá hodnotu, vrátí samotnou množinu.set.delete(value)
– odebere hodnotu, vrátítrue
pokudvalue
existoval v okamžiku volání, jinakfalse
.set.has(value)
– vrátítrue
pokud hodnota v sadě existuje, jinakfalse
.set.clear()
– odebere vše ze sady.set.size
– je počet prvků.
Hlavním rysem je opakované volání set.add(value)
se stejnou hodnotou nic nedělají. To je důvod, proč se každá hodnota objevuje v Set
pouze jednou.
Například k nám přicházejí návštěvníci a rádi bychom si na všechny vzpomněli. Opakované návštěvy by však neměly vést k duplicitám. Návštěvník musí být „započítán“ pouze jednou.
Set
je pro to to pravé:
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits, some users come multiple times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set keeps only unique values
alert( set.size ); // 3
for (let user of set) {
alert(user.name); // John (then Pete and Mary)
}
Alternativa k Set
může být pole uživatelů a kód pro kontrolu duplikátů při každém vložení pomocí arr.find. Ale výkon by byl mnohem horší, protože tato metoda prochází celým polem a kontroluje každý prvek. Set
je mnohem lépe interně optimalizován pro kontroly jedinečnosti.
Iterace přes sadu
Můžeme smyčku přes sadu buď pomocí for..of
nebo pomocí forEach
:
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// the same with forEach:
set.forEach((value, valueAgain, set) => {
alert(value);
});
Všimněte si té legrační věci. Funkce zpětného volání předána v forEach
má 3 argumenty:a value
a poté stejnou hodnotu valueAgain
a poté cílový objekt. Ve skutečnosti se stejná hodnota objeví v argumentech dvakrát.
Důvodem je kompatibilita s Map
kde zpětné volání prošlo forEach
má tři argumenty. Vypadá to trochu divně, určitě. Může však pomoci nahradit Map
s Set
v určitých případech s lehkostí a naopak.
Stejné metody Map
má pro iterátory jsou také podporovány:
set.keys()
– vrací iterovatelný objekt pro hodnoty,set.values()
– stejně jakoset.keys()
, kvůli kompatibilitě sMap
,set.entries()
– vrací iterovatelný objekt pro položky[value, value]
, existuje kvůli kompatibilitě sMap
.
Shrnutí
Map
– je sbírka klíčovaných hodnot.
Metody a vlastnosti:
new Map([iterable])
– vytvoří mapu s volitelnýmiterable
(např. pole)[key,value]
páry pro inicializaci.map.set(key, value)
– uloží hodnotu pomocí klíče, vrátí samotnou mapu.map.get(key)
– vrátí hodnotu pomocí klíče,undefined
pokudkey
na mapě neexistuje.map.has(key)
– vrátítrue
pokudkey
existuje,false
jinak.map.delete(key)
– odebere hodnotu pomocí klíče, vrátítrue
pokudkey
existoval v okamžiku hovoru, jinakfalse
.map.clear()
– odstraní vše z mapy.map.size
– vrátí aktuální počet prvků.
Rozdíly od běžného Object
:
- Jakékoli klíče, objekty mohou být klíče.
- Další pohodlné metody,
size
vlastnictví.
Set
– je sbírka jedinečných hodnot.
Metody a vlastnosti:
new Set([iterable])
– vytvoří sadu s volitelnýmiterable
(např. pole) hodnot pro inicializaci.set.add(value)
– přidá hodnotu (nedělá nic, pokudvalue
existuje), vrátí samotnou množinu.set.delete(value)
– odebere hodnotu, vrátítrue
pokudvalue
existoval v okamžiku volání, jinakfalse
.set.has(value)
– vrátítrue
pokud hodnota v sadě existuje, jinakfalse
.set.clear()
– odebere vše ze sady.set.size
– je počet prvků.
Iterace přes Map
a Set
je vždy v pořadí vložení, takže nemůžeme říci, že tyto kolekce jsou neuspořádané, ale nemůžeme změnit pořadí prvků nebo přímo získat prvek podle jeho čísla.