class: left, middle background-image: url(../../images/master-folie.png) .title[ ## Maps and Sets
### Oleg Varaksin #### ovaraksin@googlemail.com ] --- # Maps (1/6). In ES5, the major drawback with objects-as-maps is the inability to use a non-string value as the key. Example: ```js var m = {}; var x = { id: 1 }, y = { id: 2 }; m[x] = "foo"; m[y] = "bar"; m[x]; // "bar" m[y]; // "bar" ``` The two objects `x` and `y` both stringify to `"[object Object]"`, so only that one key is being set in `m`. In ES6, you can use `Map(...)` to fix this problem. Map is a key/value data structure. --- # Maps (2/6). The `Map` has `set(...)` and `get(...)` methods. ```js var m = new Map(); var x = { id: 1 }, y = { id: 2 }; m.set(x, "foo"); m.set(y, "bar"); m.get(x); // "foo" m.get(y); // "bar" ``` You're able to use anything for the keys. You're not just limited to primitive values like symbols, numbers, or strings, but you can even use functions, objects and dates too. ```js var map = new Map(); map.set(new Date(), function today() {}); map.set(() => 'key', {pony: 'foo'}); map.set(Symbol('items'), [1, 2]); ``` --- # Maps (3/6). To delete an element from a map, use the `delete(...)` method. ```js m.set(x, "foo"); m.set(y, "bar"); m.delete(y); ``` To determine if a map has a given key, use `has(...)`. ```js m.has(y) // false ``` You can clear the entire map's contents with `clear()`. To get the length of a map (i.e., the number of keys), use the `size` property. ```js m.set(x, "foo"); m.set(y, "bar"); m.size; // 2 m.clear(); m.size; // 0 ``` --- # Maps (4/6). The `Map(...)` constructor can receive an iterable, which must produce a list of arrays, where the first item in each array is the key and the second item is the value. ```js var x = { id: 1 }, y = { id: 2 }; var m = new Map([[x, "foo"], [y, "bar"]]); m.get(x); // "foo" m.get(y); // "bar" ``` This format for iteration is identical to that produced by the `entries()` method. That makes it easy to make a copy of a map: ```js var m2 = new Map(m.entries()); var m3 = new Map(m); // the same as above ``` You don't need to explicitly iterate over `.entries()` because ```js map[Symbol.iterator] === map.entries // => true ``` --- # Maps (5/6). You can iterate over `Map` entries in multiple ways. `for..of` loop combined with destructuring. ```js var map = new Map(); map.set('s', 'o'); map.set('m', 'e'); for (let [key, value] of map) { console.log(`${key}: ${value}`); } // <- 's: o' // <- 'm: e' ``` `.forEach` method (same as for array). ```js var map = new Map([[NaN, 1], [Symbol(), 2]]); map.forEach((value, key) => console.log(key, value)); // <- NaN 1 // <- Symbol() 2 ``` __Note:__ Map's entries are always iterated in insertion order! This is in contrast with `Object.keys` or `for..in` loops which follow an arbitrary order. --- # Maps (6/6). Just like `.entries()`, Map has two other iterators you can leverage. - Method `.keys()` to get the list of keys. - Method `.values()` to get the list of values. ```js var m = new Map(); var x = { id: 1 }, y = { id: 2 }; m.set(x, "foo"); m.set(y, "bar"); var keys = [...m.keys()]; var vals = [...m.values()]; keys[0] === x; // true keys[1] === y; // true vals[0] === "foo"; // true vals[1] === "bar"; // true ``` --- # WeakMaps (1/2). `WeakMap` holds references to its keys weakly, meaning that if there are no other references to one of its keys, the object is subject to garbage collection. Keeping data about DOM elements that should be released from memory when they're no longer of interest is very important use case of `WeakMap`. If you have DOM elements as keys in a normal `Map`, the DOM elements don't get released from memory because the `Map` still keeps references to them. There are a few limitations on `WeakMap` in comparison to `Map`. - `WeakMap` is not iterable. There are no `.entries()`, no `.keys()`, no `.values()`, no `.forEach()`. - There is no `.clear()`. - There is no `.size()`. - Every key must be an object. ```js var map = new WeakMap(); map.set(1, 2); // TypeError: 1 is not an object! ``` --- # WeakMaps (2/2). You are still able to pass an iterable to populate a `WeakMap` through its constructor. ```js var map = new WeakMap([[new Date(), 'foo'], [() => 'bar', 'baz']]); ``` Just like with `Map`, you can use `.has()`, `.get()`, and `.delete()` too. It's important to note that a `WeakMap` only holds its keys weakly, not its values. ```js var m = new WeakMap(); var x = { id: 1 }, y = { id: 2 }, z = { id: 3 }, w = { id: 4 }; m.set(x, y); x = null; // {id: 1} is GC-eligible y = null; // {id: 2} is GC-eligible only because {id: 1} is m.set(z, w); w = null; // {id: 4} is not GC-eligible ``` --- # Sets (1/3). A set is a collection of unique values (duplicates are ignored). The API for a set is similar to map. `The add(...)` method takes the place of the `set(...)` method, and there is no `get(...)` method. ```js var s = new Set(); var x = { id: 1 }, y = { id: 2 }; s.add(x); s.add(y); s.add(x); s.size; // 2 s.delete(y); s.size; // 1 s.has(y); // false s.clear(); s.size; // 0 ``` --- # Sets (2/3). The `Set(...)` constructor form is similar to `Map(...)`, in that it can receive an iterable, like another set or simply an array of values. However, unlike `Map(...)` which expects entries list (array of key/value arrays), `Set(...)` expects a values list (array of values): ```js var x = { id: 1 }, y = { id: 2 }; var s = new Set([x, y]); ``` Sets have the same iterator methods as maps. The `keys()` and `values()` iterators both yield a list of the unique values in the set. The `entries()` iterator yields a list of items like `[value, value]`. The default iterator for a set is its `values()` iterator. ```js set[Symbol.iterator] === set.values // true set.keys === set.values // true ``` --- # Sets (3/3). ```js var s = new Set(); var x = { id: 1 }, y = { id: 2 }; s.add(x).add(y); var keys = [...s.keys()], vals = [...s.values()]; keys[0] === x; keys[1] === y; vals[0] === x; vals[1] === y; ``` The following piece of code creates a `Set` with all the `div` elements on a page. ```js function divs () { return [...document.querySelectorAll('div')]; } var set = new Set(divs()); ``` --- # WeakSets. WeakSet holds its values weakly (there aren't really keys). ```js var s = new WeakSet(); var x = { id: 1 }, y = { id: 2 }; s.add(x); s.add(y); x = null; // x is GC-eligible y = null; // y is GC-eligible ``` __Important:__ - `WeakSet` values must be objects, not primitive values as it is allowed with Sets. - You can't iterate over `WeakSet`. Like in `WeakMap`, you can only use `.add()`, `.has()`, and `.delete()`. --- class: center, middle, inverse # That's all folks, thanks for your attention! .back[[Back to the homepage](http://ova2.github.io/es6-presentations/)] Slideshow created with [remark](https://github.com/gnab/remark)