ES6 – set, map, weak

javascript

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.