JavaScript has never offered a variety of data structures out of the box. If you needed any structure fancier than an array, you'd have to roll your own or borrow some code.
The 2015 specification brings a number of keyed collections to the language, including the Map, Set, WeakMap, and WeakSet.
Although you can treat any JavaScript object as a dictionary or key-value store, there are some drawbacks. Most notably, the only type of keys you can use to index into an object are keys of type string. The new Map collection removes the restriction on key types and offers some additional conveniences. The core of the API revolves around the get and set methods.
let map = new Map(); // add a new key-value pair map.set("key", 301); // overwrite an existing value map.set("key", 302); expect(map.get("key")).toBe(302);
The delete and has methods are also handy.
let map = new Map(); map.set("key", 611); expect(map.has("key")).toBe(true); map.delete("key"); expect(map.has("key")).toBe(false);
A key can now be any type of object.
var someKey = { firstName: "Scott"}; var someValue = { lastName: "Allen"}; var map = new Map(); map.set(someKey, someValue); expect(map.size).toBe(1); expect(map.get(someKey).lastName).toBe("Allen"); expect(map.get({firstName:"Scott"})).toBeUndefined();
When using objects as keys, JavaScript will compare pointers behind the scenes, as you probably expect (and as the above code demonstrates).
You can also construct a Map using a two dimensional array of key-value pairs.
let map = new Map([ [1, "one"], [2, "two"], [3, "three"] ]);
Finally, maps are iterable in a number of ways. You can retrieve an iterator for the keys in a map (using the keys method), the values in a map (using the values method), and the key-value entries in a map (using the entries method, as you might have guessed). The map also has the magic [Symbol.iterator] method, so you can use a Map directly in a for of loop.
let map = new Map([ [1, "one"], [2, "two"], [3, "three"] ]); let sum = 0; let combined = ""; for(let pair of map) { sum += pair[0]; combined += pair[1]; } expect(map.size).toBe(3); expect(sum).toBe(6); expect(combined).toBe("onetwothree"); map.clear(); expect(map.size).toBe(0);
Note how each entry comes out as an array with the key in index 0 and the value in index 1. This is a scenario where destructuring is handy.
for(let [key, value] of map) { sum += key; combined += value; }
Just like in mathematics, a Set maintains a collection of distinct objects. The API is slightly different from a Map. For example, you can construct a set by passing in any iterator.
let animals = ["bear", "snake", "elephant", "snake"]; let animalsSet = new Set(animals.values()); expect(animals.length).toBe(4); expect(animalsSet.size).toBe(3); expect(animalsSet.has("bear")).toBe(true); animalsSet.delete("bear"); expect(animalsSet.size).toBe(2);
Just like a Map, a Set compares object types by comparing references. Unlike other languages, there is no ability to provide an alternative comparer strategy.
let scott = { name: "Scott "}; let people = new Set(); people.add(scott); people.add(scott); people.add( {name:"Scott"}); expect(people.size).toBe(2); expect(people.has(scott)).toBe(true); expect(people.has({name:"Scott"})).toBe(false);
The WeakMap and WeakSet collections serve a similar purpose to their non-weak counterparts, but the objects held in these collections are eligible for garbage collection at any given point in time, making these weak collections ideal for many extensibility scenarios. The special nature of weak collections means you cannot iterate over the collections, because an entry could disappear before the iteration completes. There are also some other small API differences. For example, the keys for a Map and the values for a Set can only be object types (no primitives like strings), and there is no size method to uncover the number of entries in these collections.
Coming up soon - more stimulating API additions in 2015 for objects, strings, and arrays.