Object vs Map
SJ W
Posted on March 20, 2024
Introduction
With the fourth post of the blog, I would like to comprehensively go over the concepts of both objects and Maps and compare them, highlighting their strengths, weaknesses, and the situations in which you may pick and utilize one accordingly.
The main reason why I decided to go over this topic is that I couldn't initially tell the difference between the two, since they both seem to operate in a very similar way - they both are a collection of key-value pairs. Actually, I was aware of the existence of the object - the most fundamental, widely-used data structure in JavaScript by far, thanks to my experience with JS for the last few years, however, during the bootcamp, I came to learn the existence of something called a Map, a data structure that seems almost the same as the object at face value. Upon its introduction, there was some confusion as to which one is appropriate to use for various circumstances, and that was essentially the catalyst that made me decide to delve into learning both comprehensively, so that I can ultimately become a better programmer! Plus, it is always fun to learn something new and pick up the programming repertoire.
Basics of Object and Map
Object
An Object is something that you definitely have heard of, if you have ever dabbled with OOP (Object-Oriented Programming) in general, and it is by far the most popular data structure in JavaScript. It is not exclusively available to JS, and any OOP language enables you to instantiate an object by first declaring a class as a template for creating objects of a certain type. It is essentially a collection of key-value pairs. While there are only a handful of data types that can be used as keys, you can store almost anything as a value. It is tightly integrated into the core of JavaScript and often used with JSON for various purposes.
Every object is unique in a sense that it has its own properties and values that set it apart from one another. Changing its structure or the value of a key of one object doesn't necessarily update another object nearby.
Inheritance is one of the fundamental concept of OOP. It allows an object to inherit the properties of its parent object, thus avoiding the duplication of various properties. An object can be generated and inherit the properties of one object with the following:
const grandparent = { familyName: "Smith" };
const parent = Object.create(grandparent);
console.log(parent.familyName); // "Smith"
parent.firstName = "John";
const child = Object.create(parent);
child.age = 25;
console.log(child.firstName); // "John" - inherited from parent
console.log(child.familyName); // "Smith" - inherited from grandparent
As you can see in the example above, there are total three objects: grandparent, parent, and child. You can immediately deduce that "parent" is an object created with using the "grandparent" object, which makes the "parent" object inherit all the properties of the "grandparent" object - it has the property called "familyName". And for the next part, an object "child" is generated using the object "parent", which basically inherits all the properties of its parent object, which is "parent". Since the concept of Inheritance refers to inheriting all the properties of parent objects that came before, the object "child" gets to access the property "familyName" of the object "grandparent". JS implements the concept of inheritance on objects using something called Prototype. Every object in JS has a property called Prototype, which is basically a reference to its parent object. Accessing the property "prototype" of an object allows one to access its parent object in its entirety. That means that you can also access the parent object of its parent object using the property "prototype". This is what we call the prototype chain. One thing you have to be aware of is that it can cause some degradation in performance, if there are too many objects chained to one another, since there is no limit to how many objects a single object can inherit from. I will stop talking about inheritance here before I go too deep into it. If you are interested in learning more about Prototype, feel free to check out this link and learn more.
// Using Class
class Person {
constructor(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`);
}
}
const person = new Person("John", "Doe", 30);
person.greet(); // Outputs: Hello, my name is John Doe.
// Using curly brackets
const person = {
firstName: "John",
lastName: "Doe",
age: 30,
greet: function() {
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`);
}
};
person.greet(); // Outputs: Hello, my name is John Doe.
In JS, you can create an object in several ways, but the most commonly used are {} and the Class. You can simply use two curly brackets to create a simple object, which may be a convenient way of using it for storing items of key and value pair. However, with OOP in mind, if you would like to create a number of unique object's of the structural similarities, then the Class is a way to go. As you can see, an object can refer to its own properties in its functions, by using the keyword "this".
Map
Understanding what an object is makes understanding what a Map is a bit easier than the usual. Deducing from what I learned about the object previously, the Map is basically an object with the additional features that enhances the life of a developer. A Map was first introduced in ES6, and, just like object, is a collection of items of the key and value pair type. One thing cool about the Map is that it is iterable. The creator of the Map object specifically came up with the built-in mechanism to remember the order in which items are inserted to the object. The fact that it remembers its insertion order differentiates a Map from an object. A regular object doesn't have a mechanism to iterate over a collection of items without resorting to a few workarounds - Object.keys(obj)
and a for loop. However, the creator of the Map kindly included several built-in functions to make our job easier to manage it overall:
// Creating a Map
const map = new Map();
// Adding Elements
map.set('key1', 'value1');
map.set('key2', 'value2');
// Accessing Elements
console.log(map.get('key1')); // Outputs: value1
// Checking for Existence
console.log(map.has('key1')); // Outputs: true
console.log(map.has('key3')); // Outputs: false
// Removing Elements
map.delete('key1'); // Removes the entry with key 'key1'
// Getting the Size
console.log(map.size); // Outputs the number of key-value pairs
// Clearing the Map
// map.clear(); Uncomment to clear the map
// Iterating over a Map
// Using forEach
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Iterating over keys
for (let key of map.keys()) {
console.log(key);
}
// Iterating over values
for (let value of map.values()) {
console.log(value);
}
// Iterating over entries (key-value pairs)
for (let [key, value] of map.entries()) {
console.log(`${key}: ${value}`);
}
// Directly iterating over map
for (let [key, value] of map) {
console.log(`${key}: ${value}`);
}
Comparing Object to Map
Similarities
One factor that immediately stands out is that both store key-value pairs. Each key must be unique and is associated with a value of various data types. At any given point during the runtime, a program may dynamically update its contents, by adding and deleting an item, reassigning a new value to an existing key, etc. The way iteration works on both aren't entirely identical, but still, they both have mechanisms to iterate over their existing items.
Map
const mapExample = new Map();
// Adding items
mapExample.set('key1', 'value1');
mapExample.set('key2', 'value2');
// Reassigning a new value to an existing key
mapExample.set('key1', 'updatedValue1');
// Deleting an item
mapExample.delete('key2');
// Displaying the updated map
for (let [key, value] of mapExample) {
console.log(`Map - Key: ${key}, Value: ${value}`);
}
Object
const objectExample = {};
// Adding items
objectExample['key1'] = 'value1';
objectExample['key2'] = 'value2';
// Reassigning a new value to an existing key
objectExample['key1'] = 'updatedValue1';
// Deleting an item
delete objectExample['key2'];
// Displaying the updated object
for (let key in objectExample) {
console.log(`Object - Key: ${key}, Value: ${objectExample[key]}`);
}
Differences
Despite their fundamental similarities between both at face value, there lies also quite a lot of differences that make both suitable for different circumstances.
Basic Functionalities
As you saw in the code blocks above, they both do the same things differently. The creator of the Map was kind enough to implement functions that we can simply use to make our job easier, whereas an object utilizes the reserved keywords. For example, adding an item to a Map object requires one to use the function mapObj.set(key, value)
, but with the object, you can simply use the following code to implement the same functionality as the function "set" of a Map: objectExample['key1'] = 'value1';
.
Key Type
I would like to think that they are a type of Hash Map data structure, for the fact that they both store key-value pairs. However, the types of data you can use as a key are what make both different from one another. For objects, you are limited with only a handful of data types - part of primitives - as a key. However, the Map essentially gives you virtual freedom to use almost anything as a key - you can use an object as a key to a value in the Map!
One of the unique things about an object is that it does not necessarily preserve the original data type of a key. For instance, if you do obj[123] = 1
and try to get that exact key using Object.keys(obj)
, you get '123'
. So if your intention is to preserve the original data type of a key, a Map is definitely a way to go.
JSON-compatible
As the core of JS, objects are fully compatible with JSON. You can fully convert an object to a JSON string, and a JSON string back to an object without a hitch. However, that is not the case with maps. The keys of maps can be of any data type, which is a strength, but at the same time, makes it pretty much daunting to fully convert a map to a JSON string. First, you have to manully convert each key-value pair to an array of key and value, and lastly, you have to convert each key to of string type to make it fully compatible with a JSON string.
const myMap = new Map();
myMap.set('key1', 'value1');
myMap.set(2, 'value2');
const mapArray = Array.from(myMap);
const jsonString = JSON.stringify(mapArray); // Convert to a JSON string
Iteration
A Map is iterable, for the fact that there is a built-in mechanism for keeping track of its insertion order, so you can simply use either its set of functions - obj.keys(), obj.values(), obj.entries() - or a "for..of" loop to iterate over the Map object in the same order that you inserted items to the Map object.
The object, on the other hand, is not iterable. It simply has no order in which items are placed, so iterating over it requires a few workarounds to pull it off - Object.keys(objVar)
and using a for loop, however, it still doesn't maintain the insertion order of items, and can feel kind of awkward to use compared to the Map.
If you truly care about the order in which the items were inserted, then you definitely go with the Map over the object.
Performance
I am not entirely sure if comparing the performance of both the Map and the object to one another matters, if you are dealing with less than 10,000 items in a single collection, but thanks to Jonas Wilms in this StackOverflow thread, I learned that, when it involves around a million entries in each collection, the Map performs significantly better than the object.
As you can see, once it reaches the threshold of 1 million objects, there are miniscule difference in the read speed, but the write speed of a Map is about the half of that of the object. However, keep in mind that there are actually a lot of factors that may go into evaluating the performance of the both, by tweaking the various settings of the environment - the way an object is created, the type of engine used to run the code, etc. So, this is not really a sure way of saying which one is truly better than another. However, in the thread, he mentioned the following at the end of his answer:
- "Use Maps for dictionaries with lots of different, changing keys as they are slightly better than objects internally represented as hash table."
- "Use Objects for - well - objects. If you have a low number of keys and frequently access those, objects are way faster (as the engine can use inline caching, hidden classes with fixed memory layout etc.)"
In the long run, I really don't think it matters - one rarely deals with that amount of items in a single list anyway, I think. But you can see that the Map could actually perform better than the object in some extreme circumstances.
Posted on March 20, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 24, 2024
November 22, 2024