Wraz z ECMAScript2015 otrzymujemy w końcu pełnoprawne kolekcje: Set i Map. Porzucamy więc spartańskie podejście do tworzenia struktur danych. Niniejszy rozdział opisuje w jaki sposób korzystać z Set, Map, WeakSet i WeakMap.
Zobacz całą serię: Let-s talk about ECMAScript 2015
Map
Mapy to kolekcje typu key => value. Zarówno klucz, jak i jego wartość mogą być typami prymitywnymi lub obiektami / referencjami.
Nie rozwodząc się długo, spójrzmy na przykład:
let map = new Map(), val2 = 'val2', val3 = { key: 'value' }; map.set(0, 'val1'); map.set('1', val2); map.set({ key: 2 }, val3); console.log(map); // Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object {key: 'value'}}
Tworząc mapę, używamy konstruktora klasy Map. By dodać kolejną parę klucz => wartość, wywołujemy metodę set(key, value).
Kolekcję typu Map możemy również zbudować poprzez przekazanie do konstruktora klasy, tablicy kolejnych par:
let map, val2 = 'val2', val3 = { key: 'value' }; map = new Map([[0, 'val1'], ['1', val2], [{ key: 2 }, val3]]); console.log(map); // Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object {key: 'value'}}
Nie będzie pewnie dla nikogo zaskoczeniem, że aby pobrać wartość z kolekcji na podstawie klucza skorzystamy z metody get(key):
let map = new Map(), val2 = 'val2', val3 = { key: 'value' }; map.set(0, 'val1'); map.set('1', val2); map.set({ key: 2 }, val3); console.log(map.get('1')); // val2
By iterować po elementach kolekcji, możemy użyć dobrze znanej metody forEach lub posłużyć się pętlą for-of, z racji tego, że klasa Map udostępnia nam swój wbudowany iterator.
let map = new Map(), val2 = 'val2', val3 = { key: 'value' }; map.set(0, 'val1'); map.set('1', val2); map.set({ key: 2 }, val3); // forEach map.forEach(function (value, key) { console.log(`Key: ${key} has value: ${value}`); // Key: 0 has value: val1 // Key: 1 has value: val2 // Key: [object Object] has value: [object Object] }); // for-of for (let entry of map) { console.log(`Key: ${entry[0]} has value: ${entry[1]}`); // Key: 0 has value: val1 // Key: 1 has value: val2 // Key: [object Object] has value: [object Object] };
Do dyspozycji mamy również metody:
- entries() — w zasadzie wynik wywołania nie różni się niczym od powyższych przykładów. Metoda entries() zwraca całą kolekcję elementów.
- keys() — zwraca jedynie klucze wszystkich elementów.
- values() — zwraca wyłącznie wszystkie wartości.
Do sprawdzenia, czy dany klucz znajduje się w naszej kolekcji, wykorzystamy metodę has(key), która zwraca wartość typu boolean:
let map = new Map(), val2 = 'val2', val3 = { key: 'value' }; map.set(0, 'val1'); map.set('1', val2); map.set({ key: 2 }, val3); console.log(map.has(0)); // true console.log(map.has('key')); // false
By usunąć dany element z kolekcji użyjemy metody delete(key):
let map = new Map(), val2 = 'val2', val3 = { key: 'value' }; map.set(0, 'val1'); map.set('1', val2); map.set({ key: 2 }, val3); console.log(map.size); // 3 map.delete('1'); console.log(map.size); // 2
Jeżeli zechcemy usunąć wszystkie elementy z mapy, mamy do dyspozycji metodę clear():
let map = new Map(), val2 = 'val2', val3 = { key: 'value' }; map.set(0, 'val1'); map.set('1', val2); map.set({ key: 2 }, val3); console.log(map.size); // 3 map.clear(); console.log(map.size); // 0
Set
Kolekcja typu Set różni się od Map tym, że zawiera pojedyncze wartości, które na dodatek muszą być unikalne. Tutaj także mogą być to wartości prymitywne, jak i obiekty lub ich referencje.
let set = new Set(); set.add(1); set.add('1'); set.add({ key: 'value' }); console.log(set); // Set {1, '1', Object {key: 'value'}}
Klasa Set udostępnia nam podobny zbiór metod, co Map. Jednak zamiast metody set(key, value), mamy do dyspozycji metodę add(value). Kolekcję możemy również utworzyć w ramach konstruktora:
let set = new Set([1, '1', { key: 'value' }]); console.log(set); // Set {1, '1', Object {key: 'value'}}
Iterowanie po elementach kolekcji przebiega identycznie jak dla Map:
let set = new Set([1, '1', { key: 'value' }]); // forEach set.forEach(function (value) { console.log(value); // 1 // '1' // Object {key: 'value'} }); // for-of for (let value of set) { console.log(value); // 1 // '1' // Object {key: 'value'} };
Jak już wyżej wspomniałem, Set zawierać może jedynie unikalne wartości:
let set = new Set([1, 1, 1, 2, 5, 5, 6, 9]); console.log(set.size); // 5!
WeakMap
Kolekcje typu Weak pozwalają zwalniać zasoby w momencie, gdy elementy przestają istnieć. W ten sposób garbage collector jest w stanie usunąć wystąpienia danych obiektów z kolekcji i zwolnić pamięć. Kluczami kolekcji mogą być wyłącznie obiekty.
Kolekcja WeakMap udostępnia nam prawie identyczne metody, jak w przypadku zwykłej klasy Map. Różnica polega na tym, że po elementach kolekcji nie jesteśmy w stanie iterować, zarówno przy pomocy metody forEach oraz pętli for-of. Tak samo nie możemy zbadać rozmiaru kolekcji, gdyż pole size nie istnieje.
new WeakMap([iterable]) WeakMap.prototype.get(key) : any WeakMap.prototype.set(key, value) : this WeakMap.prototype.has(key) : boolean WeakMap.prototype.delete(key) : boolean
let wm = new WeakMap(), obj = { key1: { k: 'v1' }, key2: { k: 'v2' } }; wm.set(obj.key1, 'val1'); wm.set(obj.key2, 'val2'); console.log(wm); // WeakMap {Object {k: 'v1'} => 'val1', Object {k: 'v2'} => 'val2'} console.log(wm.has(obj.key1)); // true delete obj.key1; console.log(wm.has(obj.key1)); // false
WeakSet
WeakSet, to odpowiednik kolekcji Set, z taką samą możliwością zwalniania zasobów jak WeakMap. Udostępnia nam nieco prostsze API i zapewnia unikalność wartości, gdzie każda musi być obiektem.
new WeakSet([iterable]) WeakSet.prototype.add(value) : any WeakSet.prototype.has(value) : boolean WeakSet.prototype.delete(value) : boolean
Po elementach kolekcji WeakSet nie możemy również iterować, tak samo jak nie jesteśmy w stanie pobrać ich ilości.
let ws = new WeakSet(), obj = { key1: { k: 'v1' }, key2: { k: 'v2' } }; ws.add(obj.key1); ws.add(obj.key2); console.log(ws); // WeakSet {Object {k: 'v1'}, Object {k: 'v2'}} console.log(ws.has(obj.key1)); // true delete obj.key1; console.log(ws.has(obj.key1)); // false
na koniec
I to by było na tyle. Cała seria dobiegła końca. Standard ECMAScript2015 został już ratyfikowany. Pozostaje nam tylko poczekać, aż przeglądarki będą w pełni wspierać wszystkie założenia nowej składni JavaScript. Zachęcam do samodzielnego eksperymentowania z nowym API. Internet wypełnia się coraz większą ilością narzędzi i przykładów opartych o ES6, a nawet ES7.