JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.
In this article, we’ll look at some data structures that we should use in our code to make our code easier to understand.
Use Plain Objects for Dictionaries
If we want to use JavaScript objects for dictionaries and maps, then it’s a good idea to create a plain object that has no prototype before we use them as such.
To do this, we can create a plain object with Object.create
method that’s has null
as the argument.
For instance, we can create a plain object as follows:
const obj = Object.create(null);
obj['foo'] = 1;
obj['bar'] = 2;
Then when we log:
console.log(obj.__proto__)
Then we see that __proto__
is undefined
, so we see that obj
doesn’t have any prototype.
Now it’s safe to use any kind of loop or method to loop through the properties like for...in
, Object.keys
, or Reflect.ownKeys
to loop through the keys of the object without accidentally stumbling onto keys of their prototype.
Use Maps to Store Key-Value Pairs With String or Non-String Keys
With ES2015, we have the new Map
object. A good feature of the Map
object is that we can add keys that aren’t strings in addition to strings.
We can also use the for...of
loop to loop through a Map
object since it’s an iterable object.
The equality of the keys is determined by the SameValueZero algorithm. With SameValueZero, it’s mostly the same as the ===
operator, except that +0 isn’t the same as 0 and -0 isn’t the same as 0.
Another good feature of maps is that keys in a Map
are ordered. The entries in a Map
is returned in the order of their insertion.
It also performs better in scenarios where there are frequent additions and removals of key-value pairs.
Also, it has methods for clearing the Map
and the size
property for getting the size.
We can also safely check if an entry exists by the key with the has
method.
For instance, we can create a map as follows:
const foo = {
a: 1
};
const bar = {
b: 1
};
const map = new Map();
map.set(foo, 1);
map.set(bar, 1);
In the code above, we have the foo
and bar
objects, which we use as keys. This can’t be done without Map
s.
Once we defined foo
and bar
as keys, then we call set
to add the entries with foo
and bar
as keys with some number as values.
Then we can call methods like clear
to clear all key-value pairs of the Map
object.
delete
can with the key be used to remove an entry with the given key. For instance, we can use it as follows:
const foo = {
a: 1
};
const bar = {
b: 1
};
const map = new Map();
map.set(foo, 1);
map.set(bar, 1);
map.delete(bar);
Then we won’t see the 2nd entry anymore. We have to reference the objects directly since the SameValueZero compares objects by reference.
Another good feature is that we can use for...of
to loop through a Map
object. For instance, if we have the following Map
:
const map = new Map();
map.set('a', 1);
map.set('b', 2);
Then we can loop through it as follows:
for (const [key, value] of map) {
console.log(key, value);
}
In the code above, we used the destructuring syntax with the key
and value
destructured as an array.
We can retrieve the key
and value
of each entry this way. Therefore, we’ll see:
a 1
b 2
from the console log output.
We can also call the Map
instance’s entries
method to get the entries the same way:
for (const [key, value] of map.entries()) {
console.log(key, value);
}
Also, we can get the array of keys with the keys
method and the values with the values
method as follows:
for (const key of map.keys()) {
console.log(key);
}
for (const value of map.values()) {
console.log(value);
}
Conclusion
We can create maps and dictionaries with the Object.create(null)
call. Also, since ES2015, we can use the Map
object, which can use non-string keys in addition to string and symbol keys.
Items in Map
s are returned in the order they’re inserted so that the order is guaranteed. Also, there’re methods to check for values and iterating through them.