Categories
Modern JavaScript

Best of Modern JavaScript — Maps

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at how to use maps.

Map Size

We can get the size of a map by using the size property.

For instance, we can write:

const map = new Map();
map.set('foo', 1);
map.set('bar', 2);

console.log(map.size);

map.size would return 2.

Clearing Maps

We can clear maps with the clear method.

For example, we can write:

map.clear();

to remove all the entries from a map.

Setting up a Map

There are 2 ways to set up a map.

One way is to pass in an array with key-value pairs in an array.

For example, we can write:

const map = new Map([
  ['foo', 'one'],
  ['bar', 'two'],
  ['baz', 'three'],
]);

Another way to create a map is to call the set method.

For instance, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

set takes a key and its value as its argument.

They both put 3 entries with the given key-value pairs.

Map Keys

Map keys can be anything, even an object.

For example, we can write:

const map = new Map()

const KEY1 = {};
map.set(KEY1, 'foo');

const KEY2 = {};
map.set(KEY2, 'bar');

We created 2 objects and use them as keys.

Then we can call get with those objects to get the value.

Then we can write:

console.log(map.get(KEY1));

to get the value with key KEY1 .

We should see 'foo' logged.

The key lookup is done with the SameValueZero algorithm, so it’s mostly the same as === but NaN is considered to be equal to itself.

This means that objects are considered equal only if they reference the same thing in memory.

For example, if we have:

const map = new Map()
map.set(NaN, 'foo');

console.log(map.get(NaN));

Then the console log will log 'foo' since we added an entry with NaN as the key and NaN is considered to be equal to itself.

-0 and +0 are considered to be the same value.

So if we have:

const map = new Map()
map.set(-0, 'foo');

console.log(map.get(+0));

Then we get 'foo' logged.

Trying to get an entry with a key that isn’t in the map returns undefined .

For example, if we write:

new Map().get('foo')

Then we get undefined .

Iterating Over Maps

We can iterate over maps by using the for-of loop.

To loop through the keys, we can get them with the map.keys() method.

For example, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

for (const key of map.keys()) {
  console.log(key);
}

Then we get 'foo' , 'bar' , and 'baz' logged.

The values method returns a iterator with the values of the map.

For example, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

for (const value of map.values()) {
  console.log(value);
}

Then we get 'one' , 'two' , 'three' logged.

The entries method returns an iterator with the key-value pairs in an array.

For example, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

for (const [key, value] of map.entries()) {
  console.log(key, value);
}

We destructured the key and value and logged them, so we get:

foo one
bar two
baz three

logged.

Conclusion

We can use maps in various. We can add, update, get, and remove entries from them.

Also, we can loop through them in various ways.

Categories
Modern JavaScript

Best of Modern JavaScript — Maps and WeakMaps

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at maps and weak maps.

Iterating and Looping Through Maps

We can iterate and loop through maps with various methods.

Map.prototype.entries returns an iterable object with arrays of key-value pair arrays for each entry of our map.

Map.prototype.forEach takes a callback that has the signature (value, key, collection) to let us loop through the map.

value has the value of the map entry.

key has the key of the map entry.

collection has the map itself.

The 2nd argument of it is the value of this inside the callback.

Map.prototype.keys returns an iterable with the keys of the map.

Map.prototype.values returns an iterable of all values in the map.

Map.prototype[Symbol.iterator] is the default method for iterating through maps.

It returns an iterable with the key-value pair arrays.

WeakMap

WeakMaps work mostly like maps.

WeakMaps keys are objects. They’re weakly held.

We can’t get an overview of all the entries with a WeakMap.

And we can’t clear a WeakMap.

We’ve to put in objects as keys, so we can’t write;

const wm = new WeakMap()

wm.set('foo', 123);

since we’ll get a TypeError doing so.

But we can write:

const wm = new WeakMap()

wm.set({}, 123);

The WeakMap keys are weakly held.

This means that an object that isn’t referred to by anything like an object or a property can be garbage collected.

We can’t access them unless they’re held somewhere.

Once the key is gone, then the entry will disappear.

We can’t get an overview of a WeakMap.

This is because there’s no way to inspect the innards of it.

The only way to get the content of a WeakMap is to get the content by the key.

The use case of a WeakMap includes things like caching, managing listeners, and keeping private data.

With WeakMaps, we can cache an object with it, as we can only have object keys.

For example, we can write:

const cache = new WeakMap();

function countOwnKeys(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  } else {
    const num = Math.random();
    cache.set(obj, num);
    return num;
  }
}

to create a WeakMap and get the entry if the key exists

Otherwise, we add an entry to the WeakMap.

We can also use them to store the listeners in a WeakMap.

For instance, we can write:

const listeners = new WeakMap();

function addListener(obj, listener) {
  if (!listeners.has(obj)) {
    listeners.set(obj, new Set());
  }
  listeners.get(obj).add(listener);
}

We have the addListener function to add an event listener to the set if it doesn’t already exist.

If obj isn’t in the WeakMap, then we create an entry the obj as the key and a set as the value.

It’s also useful for keeping private data as we need the reference to the key object to get the entry.

So we can write:

const _counter = new WeakMap();

class Countdown {
  constructor(counter) {
    _counter.set(this, counter);
  }

  increment() {
    let counter = _counter.get(this);
    if (counter < 1) {
      return;
    }
    counter++;
    _counter.set(this, counter);
  }
}

to store the counter in the WeakMap with the set method call in the constructor.

In the increment method, we get the counter with the _counter.get method.

Then we increment the counter and set the new value with set .

Conclusion

We can iterate through maps with various methods.

Also, WeakMaps can be used to store private data, caching, and more.

Categories
Modern JavaScript

Best of Modern JavaScript — Maps and Arrays

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at how to use maps by doing array operations with them.

Converting Maps to Arrays

We can use the spread operator to convert iterable objects to arrays.

This includes maps.

For example, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

const arr = [...map];

to create a map and convert it to an array.

Then we get an array with the following for arr :

[
  [
    "foo",
    "one"
  ],
  [
    "bar",
    "two"
  ],
  [
    "baz",
    "three"
  ]
]

Looping Over Map Entries

We can loop over map entries with the forEach method.

The method takes a callback with the value and key as parameters.

For instance, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

map.forEach((value, key) => {
  console.log(key, value);
});

Then we get:

foo one
bar two
baz three

from the console log.

Mapping and Filtering Maps

To map and filter maps, we’ve to convert the map to an array first.

There’re no methods to do this within the Map constructor.

Therefore, to create a map and then do filtering and mapping with it, we can write:

const map = new Map()
  .set('foo', 1)
  .set('bar', 2)
  .set('baz', 3);

const mappedMap = new Map(
  [...map]
  .map(([k, v]) => [k, v ** 2])
);

We created a map called map .

Then we spread map with the spread operator into an array.

Then we called map on the returned array instance and return a new array with the v , which has the value squared.

k is the key and that stayed the same.

We did the mapping in the Map constructor to get a map returned.

In the end, we get a map with the following:

{"foo" => 1, "bar" => 4, "baz" => 9}

Similarly, we can call filter to filter the map entries.

For example, we can write:

const map = new Map()
  .set('foo', 1)
  .set('bar', 2)
  .set('baz', 3);

const mappedMap = new Map(
  [...map]
  .filter(([k, v]) => v < 3)
);

We called the filter method with the same callback signature, but we return only the entries with the value less than 3.

We did the filtering in the Map constructor to get a map returned.

In the end, we get:

{"foo" => 1, "bar" => 2}

We can also use the spread operator to combine maps.

For instance, we can write:

const map = new Map()
  .set('foo', 1)
  .set('bar', 2)
  .set('baz', 3);

const map2 = new Map()
  .set('qux', 4)
  .set('quxx', 5);

const combinedMap = new Map([...map, ...map2])

We created 2 maps, map1 and map2 .

Then we spread them both into an array with the spread operator.

The Map constructor will turn all the entries into a map.

In the end, we get:

{"foo" => 1, "bar" => 2, "baz" => 3, "qux" => 4, "quxx" => 5}

Conclusion

Converting maps to arrays are useful for various operations.

It lets us use array methods on maps, which is useful since there’re no map equivalents of array methods.

Categories
Modern JavaScript

Best of Modern JavaScript — Maps

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at how to use maps in various ways.

Maps to JSON

Maps can be converted to JSON objects.

We can do the conversion to the spread operator to an array.

For instance, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

const arr = [...map];

We can just spread the map into an array of key-value pair arrays since maps are iterable.

Once we did that, we can convert the array into a JSON string with JSON.stringify :

console.log(JSON.stringify(arr));

Then we get:

[["foo","one"],["bar","two"],["baz","three"]]

We can convert the stringified array of key-value pair arrays into a map with JSON.parse and the Map constructor.

For example, we can write:

const parsed = new Map(JSON.parse(str));

Then we get back the original map.

Map to Object

We can convert a map to an object by looping through it and then putting the keys as the properties and the values as the values.

For example, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

const obj = Object.create(null);
for (const [k, v] of map) {
  obj[k] = v;
}

We create an object without a prototype by passing in null into Object.create .

Then we loop through our map to get the key and value and then set the key as the property name and the value as the value with the for-of loop.

We can convert the object to JSON with JSON.stringify .

For example, we can write:

console.log(JSON.stringify(obj));

And we get:

{"foo":"one","bar":"two","baz":"three"}

logged.

We can call JSON.parse to parse that back into an object.

For instance, we can write:

const map = new Map()
  .set('foo', 'one')
  .set('bar', 'two')
  .set('baz', 'three');

const obj = Object.create(null);
for (const [k, v] of map) {
  obj[k] = v;
}

const str = JSON.stringify(obj);
console.log(JSON.parse(str));

Map API

The Map API lets us create a map with an iterable object of key-value pair arrays.

This is optional.

If we don’t pass it in, then we create an empty map.

For instance, we can write:

const map = new Map([
  ['foo', 'one'],
  ['bar', 'two'],
  ['bar', 'three'],
]);

to create a map.

The Map.prototype.get method takes a string key and returns the value with the given key.

If there’s no such key in the map, then undefined is returned.

Map.prototype.set takes a key and value as arguments and then returns the map with the new entry added.

Map.prototype.has takes a key and returns a boolean indicating whether the key exists or not.

Map.prototype.delete takes a key and removes the item given the key.

If an item is removed then true is returned.

Otherwise, nothing happens and false is returned.

Map.prototype.size is a getter method and return how many entries are in the map.

Map.prototype.clear clears all entries from the map and returns nothing.

Conclusion

Maps can be converted to arrays and objects.

Also, it has many methods we can use to manipulate and get data about maps.

Categories
Modern JavaScript

Best of Modern JavaScript — Loops and Array.from

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the for-of loop and the Array.from method.

Iterating with Existing Variables, Object Properties, and Array Elements

We can iterate with variables that have already been defined.

For instance, we can write:

let x;
for (x of [1, 2, 3]) {
  console.log(x);
}

We assign the array entry to x , which is defined before the loop block.

We can also iterate with an object property.

For example, we can write:

const obj = {};
for (obj.foo of [1, 2, 3]) {
  console.log(obj.foo);
}

We defined obj outside the loop and attached the foo property to it.

We can also iterate with an array element:

const arr = [];
for (arr[0] of [1, 2, 3]) {
  console.log(arr[0]);
}

We set the first entry of arr as we loop through it.

Iterating with a Destructuring Pattern

We can iterate with the destructuring pattern.

For example, we can write:

const map = new Map().set(1, 'foo').set(2, 'bar');
for (const [key, value] of map) {
  console.log(key, value);
}

We create a Map instance with the key and value.

Then we can loop through it with the key and value destructured from each map entry.

And then we can use it anywhere in the loop body.

We can do the same with array.entries() it returns an array with entries with the [key, value] pattern.

For example, we can write:

const arr = ['foo', 'bar', 'baz'];
for (const [key, value] of arr.entries()) {
  console.log(key, value);
}

Then we destructured the return entries and log them.

New Array Features

The Array constructor gained various instance and static methods with ES6.

New Static Array Methods

One of the new static Array method includes the Array.from method.

It takes an array-like or iterable object and returns an array.

For example, we can write:

const arrayLike = {
  length: 2,
  0: 'foo',
  1: 'bar'
};

const arr = Array.from(arrayLike);
for (const a of arr) {
  console.log(a);
}

We convert the arrayLike object into an array with the Array.from method.

An array-like object is one with the length property and numeric indexes.

We can’t use these kinds of object with the for-of loop.

So if we write:

const arrayLike = {
  length: 2,
  0: 'foo',
  1: 'bar'
};

for (const a of arrayLike) {
  console.log(a);
}

We’ll get the error ‘Uncaught TypeError: arrayLike is not iterable’.

We can also convert iterable objects to arrays with Array.from .

For instance, given the following HTML:

<div>
  foo
</div>
<div>
  bar
</div>
<div>
  baz
</div>

We can get a NodeList with the div nodes, convert it to an array, and the loop through it.

For instance, we can write:

const divs = document.querySelectorAll('div');

for (const d of Array.from(divs)) {
  console.log(d.textContent);
}

We call Array.from to convert the NodeList to an array.

And then we can use it with the for-of loop.

This is different from the old way, where we call slice with the call method:

const divs = document.querySelectorAll('div');
const arr = Array.prototype.slice.call(divs);
for (const d of arr) {
  console.log(d.textContent);
}

Conclusion

We can use the for-of loop with various kinds of variables.

Also, the Array.from lets us convert an array-like or iterable object to an array.